OpenWatcom の C コンパイラの _Far16 (_Seg16) ポインタの扱いがバグってるっぽい件について
忘れないうちに書いとこう…。
テストコード : 引数の 32bit ポインタを 16bit FAR ポインタに変換し、16bit API を(サンク経由で)呼び出すって感じのコード(test16.c)
#define INCL_DOS #define INCL_DOSERRORS #include <os2.h> /* declare 16bit far pointer */ #if defined(__GNUC__) typedef ULONG PTEST16; #elif defined(__BORLANDC__) typedef void __far16 * PTEST16; #else typedef void * _Seg16 PTEST16; #endif #ifdef __cplusplus extern "C" USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */ #else extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */ #endif USHORT APIENTRY DOS32TEST_1(void *pv) { USHORT rc; rc = DOS16TEST((PTEST16)pv); return rc; } USHORT APIENTRY DOS32TEST_2(void *pv) { USHORT rc; PTEST16 pt16; pt16 = (PTEST16)pv; rc = DOS16TEST(pt16); return rc; }
これを OpenWatcom の C コンパイラ(wcc386)でコンパイルし、obj を逆アセンブルして asm を得る。
wcc386 -q -s -bt=os2 -bd -d1 test16.c && wdis -a -s=test16.c test16.obj > test16_wcc.asm
んで結果(test16_wcc.asm):
.387 .386p .model flat PUBLIC DOS32TEST_1 PUBLIC DOS32TEST_2 EXTRN __TNK:BYTE EXTRN DOS16TEST:BYTE EXTRN __Far16Func2:BYTE EXTRN __FlatToFar16:BYTE EXTRN __DLLstart_:BYTE DGROUP GROUP CONST,CONST2,_DATA _TEXT SEGMENT BYTE PUBLIC USE32 'CODE' ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP ; #define INCL_DOS ; #define INCL_DOSERRORS ; #include <os2.h> ; ; /* declare 16bit far pointer */ ; #if defined(__GNUC__) ; typedef ULONG PTEST16; ; #elif defined(__BORLANDC__) ; typedef void __far16 * PTEST16; ; #else ; typedef void * _Seg16 PTEST16; ; #endif ; ; extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */ ; ; USHORT APIENTRY DOS32TEST_1(void *pv) DOS32TEST_1: push esp push 4 push 10H call near ptr FLAT:__TNK ; prepare for thunk push esi sub esp,4 ; { ; USHORT rc; ; ; rc = DOS16TEST((PTEST16)pv); mov esi,dword ptr 10H[esp] ; Oops, casting by PTEST16 has mov dword ptr [esp],esi ; no effect on pointer... mov ecx,4 mov esi,esp mov eax,DOS16TEST call near ptr FLAT:__Far16Func2 ; invoke thunk code ; return rc; ; } add esp,4 pop esi pop esp ret ; ; USHORT APIENTRY DOS32TEST_2(void *pv) DOS32TEST_2: push esp push 4 push 10H call near ptr FLAT:__TNK push esi sub esp,4 ; { ; USHORT rc; ; PTEST16 pt16; ; ; pt16 = (PTEST16)pv; mov eax,dword ptr 10H[esp] call near ptr FLAT:__FlatToFar16 ; convert the flat pointer to _Far16 ; rc = DOS16TEST(pt16); call near ptr FLAT:__FlatToFar16 ; what!? why convert again? mov dword ptr [esp],eax mov ecx,4 mov esi,esp mov eax,DOS16TEST call near ptr FLAT:__Far16Func2 ; return rc; ; } add esp,4 pop esi pop esp ret _TEXT ENDS CONST SEGMENT DWORD PUBLIC USE32 'DATA' CONST ENDS CONST2 SEGMENT DWORD PUBLIC USE32 'DATA' CONST2 ENDS _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS END
えーと、つまり、
- 関数の引数上で直接キャストしたときは、ポインタが変換されずにそのまま渡されてる(つまり PTEST16 のキャストがまったく効いてない)
- いったん PTEST16 な変数を受け皿にしたほうは、何故か2度も変換ルーチンが呼び出されている。なんでやねん。
んで、今度は C++ コンパイラ(wpp386)でコンパイルしたときの結果(test16_wpp.asm):
.387 .386p .model flat PUBLIC `W?DOS32TEST_1$n(pnv)us` PUBLIC `W?DOS32TEST_2$n(pnv)us` EXTRN __TNK:BYTE EXTRN __FlatToFar16:BYTE EXTRN DOS16TEST:BYTE EXTRN __Far16Func2:BYTE EXTRN __DLLstart_:BYTE DGROUP GROUP CONST,CONST2,_DATA,_BSS _TEXT SEGMENT BYTE PUBLIC USE32 'CODE' ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP ; #define INCL_DOS ; #define INCL_DOSERRORS ; #include <os2.h> ; ; /* declare 16bit far pointer */ ; #if defined(__GNUC__) ; typedef ULONG PTEST16; ; #elif defined(__BORLANDC__) ; typedef void __far16 * PTEST16; ; #else ; typedef void * _Seg16 PTEST16; ; #endif ; ; #ifdef __cplusplus ; extern "C" USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */ ; #else ; extern USHORT APIENTRY16 DOS16TEST(PTEST16 pt16); /* (not exist in real) */ ; #endif ; ; USHORT APIENTRY DOS32TEST_1(void *pv) `W?DOS32TEST_1$n(pnv)us`: push esp push 4 push 10H call near ptr FLAT:__TNK push esi sub esp,4 ; { ; USHORT rc; ; ; rc = DOS16TEST((PTEST16)pv); mov eax,dword ptr 10H[esp] call near ptr FLAT:__FlatToFar16 mov dword ptr [esp],eax mov ecx,4 mov esi,esp mov eax,DOS16TEST call near ptr FLAT:__Far16Func2 ; return rc; add esp,4 pop esi pop esp ret ; } ; ; USHORT APIENTRY DOS32TEST_2(void *pv) `W?DOS32TEST_2$n(pnv)us`: push esp push 4 push 10H call near ptr FLAT:__TNK push esi sub esp,4 ; { ; USHORT rc; ; PTEST16 pt16; ; ; pt16 = (PTEST16)pv; ; rc = DOS16TEST(pt16); mov eax,dword ptr 10H[esp] call near ptr FLAT:__FlatToFar16 mov dword ptr [esp],eax mov ecx,4 mov esi,esp mov eax,DOS16TEST call near ptr FLAT:__Far16Func2 ; return rc; add esp,4 pop esi pop esp ret _TEXT ENDS CONST SEGMENT BYTE PUBLIC USE32 'DATA' CONST ENDS CONST2 SEGMENT BYTE PUBLIC USE32 'DATA' CONST2 ENDS _DATA SEGMENT BYTE PUBLIC USE32 'DATA' _DATA ENDS _BSS SEGMENT BYTE PUBLIC USE32 'BSS' _BSS ENDS END
今度はどっちの関数でも __FlatToFar16 が一度だけ呼び出されているのでした。
C のほうだけバグってるってのも何か珍しいなあ。型情報のぬるさが仇になっているのでしょうか。
報告しないと直してもらえないよなあこれ。しかし英語でどうやって……