構造体を戻り値にする __cdecl 関数はコンパイラ間の互換性がない!?
んでまあ、OpenWatcom で実数を返す __cdecl 関数についてすこし確認してみたらちょっと気になったので、手持ちのコンパイラで構造体を返す関数のコード生成をみてみたら……意外と互換性に難がありそうに思えたのでした。
とりあえず C のソース (fcdecl.c)
struct skelton { int dummy0; int dummy1; int dummy2; int p; }; struct skelton __cdecl test(void) { struct skelton tmp; tmp.p = 1; return tmp; }
.386p endif model flat endif _TEXT segment dword public use32 'CODE' _TEXT ends _DATA segment dword public use32 'DATA' _DATA ends _BSS segment dword public use32 'BSS' _BSS ends DGROUP group _BSS,_DATA _TEXT segment dword public use32 'CODE' _test proc near push ebp mov ebp,esp add esp,-16 push esi push edi mov eax,dword ptr [ebp+8] @1: mov dword ptr [ebp-4],1 lea esi,dword ptr [ebp-16] mov edi,eax mov ecx,4 rep movsd @3: @2: pop edi pop esi mov esp,ebp pop ebp ret _test endp _TEXT ends public _test end
戻り値用の構造体は呼び出し側で確保しておいて、そのポインタをスタックに積んでから関数をコール。たぶんこれが「期待される挙動」にいちばん近いと思われます。(Visual C++ もたぶんこうなると思う。だれか教えてください)
次は mingw の gcc 3.3.3 (-fomit-frame-pointer)。
.file "fcdecl.c" .text .globl _test .def _test; .scl 2; .type 32; .endef _test: subl $28, %esp movl 32(%esp), %edx movl $1, 12(%esp) movl (%esp), %eax movl %eax, (%edx) movl 4(%esp), %eax movl %eax, 4(%edx) movl 8(%esp), %eax movl %eax, 8(%edx) movl 12(%esp), %eax movl %eax, 12(%edx) movl %edx, %eax addl $28, %esp ret $4
え゛ー。
ちょ、これ、__stdcall とまったく同じ…(__cdecl を __stdcall にすると、パブリックシンボル名以外まったく同じコードが生成される)。
そして OpenWatcom は…これはこれで問題が。
.387 .386p .model flat PUBLIC _test DGROUP GROUP CONST,CONST2,_DATA _TEXT SEGMENT BYTE PUBLIC USE32 'CODE' ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP _test: push esi push edi sub esp,10H mov dword ptr 0cH[esp],1 mov eax,offset FLAT:L$1 mov edi,offset FLAT:L$1 mov esi,esp movsd movsd movsd movsd add esp,10H pop edi pop esi ret _TEXT ENDS CONST SEGMENT DWORD PUBLIC USE32 'DATA' L$1: DB 0, 0, 0, 0, 0, 0, 0, 0 DB 0, 0, 0, 0, 0, 0, 0, 0 CONST ENDS CONST2 SEGMENT DWORD PUBLIC USE32 'DATA' CONST2 ENDS _DATA SEGMENT DWORD PUBLIC USE32 'DATA' _DATA ENDS END
static の隠し構造体を関数側で確保し、リターン時にそのポインタを戻り値として渡す。
静的変数使っちゃってる時点でもう構造的にスレッドセーフじゃない。テンプレートで変数もクラスも無節操に使えちゃう C++ で __cdecl 関数を使ったら…ガクブル。
というわけで予想以上にバラバラでした(構造体のサイズが小さい場合は内容すべてをレジスタにぶっこんだりするかもしれないが、そのへんまで調べる気が失せました)。ちなみに __stdcall の場合はこういう非互換性はなかった。