C/C++函数调用约定
關(guān)于?C/C++?函數(shù)調(diào)用約定,大多數(shù)時(shí)候并不會(huì)影響程序邏輯,但遇到跨語(yǔ)言編程時(shí),了解一下還是有好處的。
VC?中默認(rèn)調(diào)用是?__cdecl?方式,Windows API?使用?__stdcall?調(diào)用方式,在?DLL?導(dǎo)出函數(shù)中,為了跟?Windows API保持一致,建議使用?__stdcall?方式。
調(diào)用約定跟堆棧清除密切相關(guān)。如果寫一個(gè)匯編函數(shù),給?C/C++?調(diào)用,在?__cdecl?方式下,則匯編函數(shù)無(wú)需清除堆棧,在?__stdcall?方式下,匯編函數(shù)需要在返回(RET)之前恢復(fù)堆棧。
C?語(yǔ)言有?__cdecl、__stdcall、__fastcall、naked、__pascal。
C++?語(yǔ)言有?__cdecl、__stdcall、__fastcall、naked、__pascal、__thiscall,比?C?語(yǔ)言多出一種?__thiscall?調(diào)用方式。
?
????在VC中,可以設(shè)置默認(rèn)的調(diào)用約定,設(shè)置路徑為:
Project?à?Properties?à?Configuration Properties?à?C/C++?à?Advanced?à?Call Conversion。
?
下面詳細(xì)介紹如上六種調(diào)用方式:
1、__cdecl
__cdecl調(diào)用約定又稱為?C?調(diào)用約定,是?C/C++?語(yǔ)言缺省的調(diào)用約定。參數(shù)按照從右至左的方式入棧,函數(shù)本身不清理?xiàng)?#xff0c;此工作由調(diào)用者負(fù)責(zé),返回值在EAX中。由于由調(diào)用者清理?xiàng)?#xff0c;所以允許可變參數(shù)函數(shù)存在,如int sprintf(char* buffer,const char* format,...);。
?
2、__stdcall
__stdcall?很多時(shí)候被稱為?pascal?調(diào)用約定。pascal?語(yǔ)言是早期很常見的一種教學(xué)用計(jì)算機(jī)程序設(shè)計(jì)語(yǔ)言,其語(yǔ)法嚴(yán)謹(jǐn)。參數(shù)按照從右至左的方式入棧,函數(shù)自身清理堆棧,返回值在EAX中。
?
3、__fastcall
顧名思義,__fastcall?的特點(diǎn)就是快,因?yàn)樗ㄟ^(guò)?CPU?寄存器來(lái)傳遞參數(shù)。他用?ECX?和?EDX?傳送前兩個(gè)雙字(DWORD)或更小的參數(shù),剩下的參數(shù)按照從右至左的方式入棧,函數(shù)自身清理堆棧,返回值在?EAX?中。
?
4、naked
naked?是一個(gè)很少見的調(diào)用約定,一般不建議使用。編譯器不會(huì)給這種函數(shù)增加初始化和清理代碼,更特殊的是,你不能用return返回返回值,只能用插入?yún)R編返回結(jié)果,此調(diào)用約定必須跟?__declspec?同時(shí)使用。例如定義一個(gè)求和程序,如__declspec(naked) int??add(int a,int b);。
?
5、__pascal
這是?pascal?語(yǔ)言的調(diào)用約定,跟?__stdcall?一樣,參數(shù)按照從右至左的方式入棧,函數(shù)自身清理堆棧,返回值在EAX中。VC?中已經(jīng)廢棄了這種調(diào)用方式,因此在寫?VC?程序時(shí),建議使用?__stdcall?代替。
?
6、__thiscall
這是?C++?語(yǔ)言特有的一種調(diào)用方式,用于類成員函數(shù)的調(diào)用約定。如果參數(shù)確定,this?指針存放于?ECX?寄存器,函數(shù)自身清理堆棧;如果參數(shù)不確定,this指針在所有參數(shù)入棧后再入棧,調(diào)用者清理?xiàng)!_thiscall?不是關(guān)鍵字,程序員不能使用。參數(shù)按照從右至左的方式入棧。
==================================================================================================
都是學(xué)習(xí)過(guò)程中做的筆記。
在編程的過(guò)程中,函數(shù)是必不可少的基礎(chǔ)之一。c語(yǔ)言的程序完全由函數(shù)構(gòu)成,所有的代碼都在某一個(gè)函數(shù)中;pascal區(qū)分函數(shù)和過(guò)程,但是本質(zhì)是類似的。而對(duì)計(jì)算機(jī)硬件而言CPU只關(guān)心一條條的指令,而不是它們是什么樣的結(jié)構(gòu)組織。call和ret只是為了函數(shù)調(diào)用的方便而已,并不是函數(shù)存在的證據(jù)。最簡(jiǎn)單的例子就是在木馬免殺過(guò)程中call+ret和jmp是等價(jià)的。因此一種高級(jí)語(yǔ)言如何實(shí)現(xiàn)函數(shù)調(diào)用并不受約束,故出現(xiàn)了不同的函數(shù)調(diào)用規(guī)則。
在windows平臺(tái)上常用的函數(shù)調(diào)用方式有pascal方式(psscal調(diào)用約定),WINAPI方式(StdCall調(diào)用約定),c方式(Cdedel調(diào)用約定)。
假設(shè)有一高級(jí)語(yǔ)言函數(shù)Message(p1,p2,p3)
?
c方式(Cdedel調(diào)用約定):
1.參數(shù)從右到左進(jìn)入堆棧;
2.在函數(shù)返回后,調(diào)用者要負(fù)責(zé)清除堆棧,所以這種調(diào)用會(huì)生成較大的可執(zhí)行程序。
push p3
push p2
push p1
call Message
add esp,0ch ;之前壓入了3個(gè)四字節(jié)的參數(shù)
?
WINAPI方式(StdCall調(diào)用約定):
????1.函數(shù)從右到左進(jìn)入堆棧;
???2.被調(diào)用的函數(shù)在返回之前自行清理堆棧,所以生成的代碼較小。
push p3
push p2
push p1
call Message
?
?
pascal方式(psscal調(diào)用約定):主要用于win16函數(shù)庫(kù)中,現(xiàn)在基本不用
????1.參數(shù)從左到右進(jìn)入堆棧
????2.被調(diào)用的函數(shù)在返回前自行清理堆棧
???3.不支持可變參數(shù)的函數(shù)調(diào)用。
push p1
push p2
push p3
call Message
?
此外在windows內(nèi)核中還有快速調(diào)用方式(_fastcall);在C++編譯的代碼中有this call方式(_thiscall)。
int a = addFun1(1,2); 004115AE push 2 004115B0 push 1 004115B2 call addFun1 (4110CDh) 004115B7 add esp,8 004115BA mov dword ptr [a],eax int a = addFun2(1,2); 004115AE push 2 004115B0 push 1 004115B2 call addFun2 (4110FAh) 004115B7 mov dword ptr [a],eax int a = addFun3(1,2); 004115AE push 2 004115B0 push 1 004115B2 call addFun3 (41105Fh) 004115B7 add esp,8 004115BA mov dword ptr [a],eax T t;int a = t.addFun4(1,2); 0041158E push 2 00411590 push 1 00411592 lea ecx,[t] //反匯編程序時(shí)只要發(fā)現(xiàn)先push ..lea ...然后又call,該調(diào)用很大可能是成員函數(shù)的調(diào)用 00411595 call T::addFun4 (4111EFh) 0041159A mov dword ptr [a],eax int a = T::addFun5(1,2); 0041158E push 2 00411590 push 1 00411592 call T::addFun5 (4111F4h) 00411597 add esp,8 0041159A mov dword ptr [a],eax 調(diào)用測(cè)試截圖:
=======================================================================================================================
VC默認(rèn)為__stdcall,?
BCB默認(rèn)為__cdecl,?
Delphi默認(rèn)為__fastcall。?
由于BCB使用Delphi的VCL庫(kù),?
所以也必須使用__fastcall。
?
| 關(guān)鍵字 | 調(diào)用規(guī)則 | 參數(shù)傳遞方向 | 返回 | 參數(shù)寄存器 | 堆棧的清除 |
| __cdecl | C調(diào)用規(guī)則 | 從右向左 | EAX | 無(wú) | 調(diào)用者 |
| __fastcall | 寄存器 | 從左向右 | EAX | EAX、EBX、ECX | 被調(diào)用者 |
| __stdcall | Win32標(biāo)準(zhǔn) | 從右向左 | EAX | 無(wú) | 被調(diào)用者 |
| __pascal | Pascal | 從左向右 | EAX | 無(wú) | 被調(diào)用者 |
| __msfastcall | Ms寄存器 | 從右向左 | EAX/EDX | ECX、EDX | 被調(diào)用者 |
?
?
摘自《面向狀態(tài)的程序設(shè)計(jì)與C++?? Builder》第四章第一節(jié)?????? Lewolf著?
1.4.1 __cdecl、_cdecl、cdecl關(guān)鍵字?
這是在C++?? Builder中特有的關(guān)鍵字,__cdecl、_cdecl、cdecl可以用來(lái)修飾變量或者函數(shù),其含義是指定的變量或者函數(shù)使用C語(yǔ)言的調(diào)用規(guī)則和命名規(guī)則。在C++?? Builder中C調(diào)用規(guī)則是缺省的設(shè)置,可以通過(guò)Project的Option菜單在Advanced?? Compiler標(biāo)簽中來(lái)改變?nèi)笔≡O(shè)置。?
對(duì)于C命名規(guī)則的變量或函數(shù),具有大小些敏感、編譯后變量名稱前下劃線前導(dǎo)符,這與Pascal語(yǔ)言是不一樣的。對(duì)于函數(shù),C調(diào)用規(guī)則要求使用堆棧傳遞參數(shù),參數(shù)傳遞的順序?yàn)樽杂蚁蜃笠来螇喝攵褩?#xff0c;由調(diào)用者自行清除堆棧,C調(diào)用規(guī)則允許向函數(shù)傳遞不定參數(shù),但這種調(diào)用方式系統(tǒng)需要更多的開銷來(lái)完成參數(shù)的匹配,除非特殊用途一般盡量不要使用,這種調(diào)用方式最典型的例子是控制臺(tái)程序中的main函數(shù),程序運(yùn)行時(shí)可以不帶參數(shù),也可以帶多個(gè)參數(shù)。?
在一個(gè)獨(dú)立的程序中,采用那種調(diào)用規(guī)則或者命名規(guī)則影響并不十分大,但是在多種語(yǔ)言混合編程,或者編寫帶有數(shù)據(jù)輸出或函數(shù)輸出的可執(zhí)行模塊(可執(zhí)行文件和動(dòng)態(tài)鏈接庫(kù))時(shí),命名規(guī)則和調(diào)用規(guī)則顯得尤為重要,使用不當(dāng)則可能導(dǎo)致程序無(wú)法正常運(yùn)行,甚至使系統(tǒng)崩潰。?
__cdecl、_cdecl、cdecl的使用語(yǔ)法如下:?
cdecl?? <data/function?? definition> ;?
_cdecl?? <data/function?? definition> ;?
__cdecl?? <data/function?? definition> ;?
例如:?
int?? cdecl?? I;???????????????????? //編譯后標(biāo)識(shí)符為“_I”?
void?? __cdecl?? Fun();?? //編譯后函數(shù)名為“_Fun”?
1.4.2 __fastcall、_fastcall關(guān)鍵字?
__fastcall和_fastcall也是C++?? Builder中特有的關(guān)鍵字,只能用于修飾函數(shù),其作用是指定函數(shù)使用“寄存器”調(diào)用規(guī)則,使用語(yǔ)法如下:?
return-type?? _fastcall?? function-name(parm-list)?
return-type?? __fastcall?? function-name(parm-list)?
例如:?
void?? __fastcall?? GetName(int?? Index);?
使用“寄存器”調(diào)用規(guī)則的函數(shù),其參數(shù)傳遞順序?yàn)樽宰笙蛴乙来蝹鬟f,并且將前三個(gè)參數(shù)盡可能的使用EAX、EBX、ECX這三個(gè)寄存器傳遞,對(duì)于前三個(gè)參數(shù)中浮點(diǎn)類型、結(jié)構(gòu)類型等超過(guò)四個(gè)字節(jié)的變量,和第四個(gè)及以后的參數(shù)則采用堆棧來(lái)傳遞,因此采用“寄存器”調(diào)用規(guī)則的函數(shù)只能傳遞固定數(shù)量的參數(shù)。?
在C++?? Builder中,所有屬于VCL的成員函數(shù),必須是__fastcall類型,編譯器將“寄存器”調(diào)用規(guī)則和C調(diào)用規(guī)則、Pascal調(diào)用規(guī)則以及Win32的標(biāo)準(zhǔn)等其它調(diào)用規(guī)則是同等對(duì)待的,因此__fastcall關(guān)鍵字不能和__cdecl、__pascal、__stdcall等關(guān)鍵字聯(lián)合使用,也不能和__export關(guān)鍵字同時(shí)使用(實(shí)際上使用__fastcall規(guī)則的函數(shù)是允許輸出的,只是這種調(diào)用規(guī)則編寫的dll或可執(zhí)行模塊只能被C++?? Builder或者Delphi開發(fā)的程序載入并調(diào)用輸出的函數(shù))。?
被指定為“寄存器”調(diào)用的函數(shù)會(huì)在編譯時(shí)被冠以前導(dǎo)符@,這將使得C及C++的函數(shù)命名過(guò)于混亂,但是這種調(diào)用規(guī)則有很高的執(zhí)行效率,因此在C++?? Builder中幾乎所有VCL的成員函數(shù)都使用這種調(diào)用規(guī)則。?
但是__fastcall和_fastcall是C++?? Builder之前的C++編譯器所沒(méi)有的調(diào)用規(guī)則,為了保證兼容性,可以使程序連接以前版本的編譯器生成的庫(kù)文件,C++?? Builder提供了-VC命令行選項(xiàng),缺省狀態(tài)下,該選項(xiàng)是關(guān)閉的,當(dāng)需要時(shí),可以通過(guò)命令行編譯并選擇-VC選項(xiàng)。?
1.4.3 __msfastcall、__msreturn關(guān)鍵字?
__msfastcall和__msreturn也是C++?? Builder中特有的關(guān)鍵字,使用這兩個(gè)關(guān)鍵字可以為程序提供和Microsoft兼容的“寄存器調(diào)用”規(guī)則以及和Microsoft兼容的函數(shù)返回的規(guī)則。?
被__msfastcall修飾的函數(shù),其參數(shù)傳遞順序?yàn)榍皟蓚€(gè)參數(shù)如果長(zhǎng)度小于4字節(jié),則分別使用ECX和EDX寄存器傳遞,其余的參數(shù)和前兩個(gè)不能使用寄存器傳遞的參數(shù)則按照從右向左的順序通過(guò)堆棧來(lái)傳遞,堆棧由被調(diào)用函數(shù)負(fù)責(zé)清除。?
__msreturn是使用Microsoft兼容的“寄存器調(diào)用”返回規(guī)則,在這種返回規(guī)則中,返回值的長(zhǎng)度在4~8個(gè)字節(jié)時(shí),將采用EAX/EDX寄存器返回函數(shù)值。?
1.4.4 __pascal、_pascal、pascal關(guān)鍵字?
__pascal、_pascal、pascal關(guān)鍵字表示一個(gè)變量或者函數(shù)遵循Pascal的調(diào)用和命名規(guī)則。?
在Pascal的命名規(guī)則中,對(duì)大小寫并不敏感,而且不象C調(diào)用規(guī)則,會(huì)在變量和函數(shù)明成前面加前導(dǎo)符。Pascal調(diào)用規(guī)則中,參數(shù)的傳遞是按照從左到右的順序依次壓入堆棧,并且由被調(diào)用函數(shù)自行清除堆棧。?
Pascal調(diào)用規(guī)則破壞了C語(yǔ)言的本身的調(diào)用體系,同時(shí)也帶來(lái)了更多的靈活性,被表示為__pascal調(diào)用規(guī)則的函數(shù),編譯后的目標(biāo)文件中,名稱會(huì)被全部轉(zhuǎn)換為大寫字母,在Pascal語(yǔ)言中,過(guò)程和函數(shù)的書寫是對(duì)大小些不敏感的,但是在C++?? Builder中采用Pascal調(diào)用規(guī)則的函數(shù),在C++源文件中仍然需要遵循C語(yǔ)言中對(duì)大小些必須嚴(yán)格一致的要求,Pascal調(diào)用規(guī)則的大小寫不敏感性只能體現(xiàn)在動(dòng)態(tài)鏈接庫(kù)、或者輸出、輸入函數(shù)以及數(shù)據(jù)的時(shí)侯。也正是這樣的原因,對(duì)一些采用Object?? Pascal編寫的第三方控件或者書寫不是很規(guī)范的Pascal程序代碼,使用C++?? Builder編譯的時(shí)候,會(huì)出現(xiàn)找不到函數(shù)原型的錯(cuò)誤,實(shí)際上可能只是程序中函數(shù)名稱的大小些不一致造成的。?
1.4.5 __stdcall、_stdcall關(guān)鍵字?
在Windows程序設(shè)計(jì)中,有一種Win32的標(biāo)準(zhǔn)調(diào)用規(guī)則,基本上所有的Windows?? API函數(shù)都采用了這種Win32的標(biāo)準(zhǔn)調(diào)用規(guī)則,在講__declspec(nothrow)關(guān)鍵字時(shí)的例子,其中宏定義就是C++?? Builder中winuser.h宏定義。Win32標(biāo)準(zhǔn)調(diào)用在C++?? Builder中以__stdcall、_stdcall關(guān)鍵字來(lái)標(biāo)識(shí)。?
在__stdcall、_stdcall調(diào)用規(guī)則中,函數(shù)的參數(shù)是按照從右向左的順序依次壓入堆棧,所有的參數(shù)均采用堆棧傳遞,而且是被調(diào)用函數(shù)負(fù)責(zé)清除堆棧。在參數(shù)的傳遞順序上,Win32標(biāo)準(zhǔn)調(diào)用規(guī)則和C調(diào)用規(guī)則一樣,從右向左,但和C調(diào)用規(guī)則不同的是,Win32的標(biāo)準(zhǔn)調(diào)用要求調(diào)用者必須嚴(yán)格按照被調(diào)用函數(shù)的參數(shù)數(shù)量及類型進(jìn)行參數(shù)傳遞。?
關(guān)于C++?? Builder中函數(shù)調(diào)用規(guī)則以及命名規(guī)則的詳細(xì)內(nèi)容,讀者參考相關(guān)書籍,這里不做過(guò)多講解,下表給出了C++?? Builder中幾種調(diào)用規(guī)則的一個(gè)簡(jiǎn)單比較,具有匯編語(yǔ)言基礎(chǔ)的讀者可以參考本書光盤中相關(guān)代碼自行分析區(qū)別。?
關(guān)鍵字?? 調(diào)用規(guī)則 參數(shù)傳遞方向 返回 參數(shù)寄存器 堆棧的清除?
__cdecl?? C調(diào)用規(guī)則 從右向左 EAX 無(wú) 調(diào)用者?
__fastcall 寄存器 從左向右 EAX EAX、EBX、ECX 被調(diào)用者?
__stdcall Win32標(biāo)準(zhǔn) 從右向左 EAX 無(wú) 被調(diào)用者?
__pascal Pascal 從左向右 EAX 無(wú) 被調(diào)用者?
__msfastcall Ms寄存器 從右向左 EAX/EDX ECX、EDX 被調(diào)用者
總結(jié)
以上是生活随笔為你收集整理的C/C++函数调用约定的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 聊一聊ws2_32.dll和wsock3
- 下一篇: 函数调用方式__stdecl _stdc