gcc為何有時會在call前push eax,默認的調用約定不是cdecl?
我的程序需要把一個C函數export給一個LLVM的JIT函數,希望遵守的約定是cdecl。
C源碼如下:
DVM_ObjectRef SoNewArray(BINT ty,BINT dim)
{
DVM_TypeSpecifier *type
= curthread-&>current_executable-&>executable-&>type_specifier[ty];
DVM_ObjectRef barray;
barray = SoCreateArray(curdvm, dim, type);
return barray;
}
但是LLVM程序調用這個代碼後報錯,反彙編以上這段代碼,結果如下:
結尾不是一般cdecl的ret而是 stdcall的 ret 4而在其它C函數調用它是,比如
push ebp
mov ebp,esp
push ebx
sub esp,0x34
mov eax,0xfffffffc
mov eax,DWORD PTR gs:[eax]
mov eax,DWORD PTR [eax+0x1c]
mov eax,DWORD PTR [eax]
mov eax,DWORD PTR [eax+0x28]
mov edx,DWORD PTR [ebp+0xc]
shl edx,0x4
add eax,edx
mov DWORD PTR [ebp-0xc],eax
mov edx,DWORD PTR ds:0x8e49940
mov ebx,DWORD PTR [ebp+0x8]
lea eax,[ebp-0x20]
mov ecx,DWORD PTR [ebp-0xc]
mov DWORD PTR [esp+0xc],ecx
mov ecx,DWORD PTR [ebp+0x10]
mov DWORD PTR [esp+0x8],ecx
mov DWORD PTR [esp+0x4],edx
mov DWORD PTR [esp],eax
call 0x8090f6f &
sub esp,0x4
mov eax,DWORD PTR [ebp-0x20]
mov edx,DWORD PTR [ebp-0x1c]
mov DWORD PTR [ebx],eax
mov DWORD PTR [ebx+0x4],edx
mov eax,DWORD PTR [ebp+0x8]
mov ebx,DWORD PTR [ebp-0x4]
leave
ret 0x4
反彙編為:
extern "C" void ExGoMain()
{
SoNewArray(0,0);
}
0x8087a81 push ebp
0x8087a82 mov ebp,esp
0x8087a84 sub esp,0x38
0x8087a87 lea eax,[ebp-0x28]
0x8087a8a mov DWORD PTR [esp+0x8],0x0
0x8087a92 mov DWORD PTR [esp+0x4],0x0
0x8087a9a mov DWORD PTR [esp],eax
0x8087a9d call 0x80912d6 &
0x8087aa2 sub esp,0x4
。。。。。。。。。。。。。。
可以看到除了把兩個參數(0,0)push 進去外,還push進了eax,這個eax是什麼鬼?他直接導致堆棧比正常的cdecl多一個int,使得我的llvm無法調用這個函數補充:我在聲明這個C函數時已經寫了DVM_ObjectRef SoNewArray(BINT ty,BINT dim) __attribute__((cdecl));
題主在嘗試給Diksam加LLVM JIT么?志同道合啊,請多多交流 ^_^
題主可能對這個函數調用有點想當然了,以為函數調用要傳遞的只是顯式寫在參數列表裡的參數。實則不然:這裡的SoNewArray()函數返回的DVM_ObjectRef是個struct:typedef struct {
DVM_VTable *v_table;
DVM_Object *data;
} DVM_ObjectRef;
其實是在調用函數(caller)一側的棧幀里分配了空間給這個用於返回的struct,並將其作為隱含參數傳遞給被被調用函數(callee)。也就是說,題主寫的SoNewArray()雖然原本聲明為:
DVM_ObjectRef SoNewArray(BINT ty, BINT dim) __attribute__((cdecl));
但實際上它生成出來的代碼從傳遞參數的角度看更像是:
void SoNewArray(DVM_ObjectRef* ret, BINT ty, BINT dim) __attribute__((cdecl));
結合這個再去應用cdecl,就可以知道題主的理解差在哪裡了——額外傳遞的參數其實就是指向分配給返回struct值的空間的指針。
所以題主給的例子:extern "C" void ExGoMain()
{
SoNewArray(0, 0);
}
其實更像是:
extern "C" void ExGoMain()
{
DVM_ObjectRef ref; // allocate space for the return value from SoNewArray()
SoNewArray(ref /* hidden argument */, 0, 0);
}
When a return is made in memory the caller passes a pointer to the memory location as the first parameter (hidden). The callee populates the memory, and returns the pointer. The callee pops the hidden pointer from the stack when returning.
注意這是針對32位x86的。不要把上述文檔泛化到x86-64上…
另外再放個傳送門:Incompatibilities這裡有提到題主說的-fpcc-struct-return / -fno-pcc-struct-return。R大已經解釋得夠清楚了,我再講講自己另一種解決辦法,就是在gcc編譯時加入 -fno-pcc-struct-return,這樣就成為我所期望的沒有push eax的版本了,貌似是通過堆棧存返回值?
其實我很在意你返回的是啥?如果不是指針 基本類型的話會進行copy 效率慢了 所以在調用之前 先引用了一個 用來存放就變三個參數了
推薦閱讀:
※現代C/C++編譯器有多智能?能做出什麼厲害的優化?
※參加2017年在新浪舉辦的北京場「前端體驗大會」是個什麼樣的體驗?