日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【转】调用约定__cdecl、__stdcall和__fastcall的区别

發(fā)布時(shí)間:2023/12/10 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】调用约定__cdecl、__stdcall和__fastcall的区别 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

什么是調(diào)用約定

函數(shù)的調(diào)用約定,顧名思義就是對函數(shù)調(diào)用的一個(gè)約束和規(guī)定(規(guī)范),描述了函數(shù)參數(shù)是怎么傳遞和由誰清除堆棧的。它決定以下內(nèi)容:(1)函數(shù)參數(shù)的壓棧順序,(2)由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧,(3)以及產(chǎn)生函數(shù)修飾名的方法。

歷史背景

在微機(jī)出現(xiàn)之前,計(jì)算機(jī)廠商幾乎都會提供一份操作系統(tǒng)和為不同編程語言編寫的編譯器。平臺所使用的調(diào)用約定都是由廠商的軟件實(shí)現(xiàn)定義的。 在Apple Ⅱ出現(xiàn)之前的早期微機(jī)幾乎都是“裸機(jī)”,少有一份OS或編譯器的,即是IBM PC也是如此。IBM PC兼容機(jī)的唯一的硬件標(biāo)準(zhǔn)是由Intel處理器(8086, 80386)定義的,并由IBM分發(fā)出去。硬件擴(kuò)展和所有的軟件標(biāo)準(zhǔn)(BIOS調(diào)用約定)都開放有市場競爭。 一群獨(dú)立的軟件公司提供了操作系統(tǒng),不同語言的編譯器以及一些應(yīng)用軟件。基于不同的需求,歷史實(shí)踐和開發(fā)人員的創(chuàng)造力,這些公司都使用了各自不同的調(diào)用約定,往往差異很大。 在IBM兼容機(jī)市場洗牌后,微軟操作系統(tǒng)和編程工具(有不同的調(diào)用約定)占據(jù)了統(tǒng)治地位,此時(shí)位于第二層次的公司如Borland和Novell,以及開源項(xiàng)目如GCC,都還各自維護(hù)自己的標(biāo)準(zhǔn)。互操作性的規(guī)定最終被硬件供應(yīng)商和軟件產(chǎn)品所采納,簡化了選擇可行標(biāo)準(zhǔn)的問題。

調(diào)用者清理

在這些約定中,調(diào)用者自己清理堆棧上的參數(shù)(arguments),這樣就允許了可變參數(shù)列表的實(shí)現(xiàn),如printf()。

cdecl

cdecl(C declaration,即C聲明)是源起C語言的一種調(diào)用約定,也是C語言的事實(shí)上的標(biāo)準(zhǔn)。在x86架構(gòu)上,其內(nèi)容包括:

  • 函數(shù)實(shí)參在線程棧上按照從右至左的順序依次壓棧。
  • 函數(shù)結(jié)果保存在寄存器EAX/AX/AL中
  • 浮點(diǎn)型結(jié)果存放在寄存器ST0中
  • 編譯后的函數(shù)名前綴以一個(gè)下劃線字符
  • 調(diào)用者負(fù)責(zé)從線程棧中彈出實(shí)參(即清棧)
  • 8比特或者16比特長的整形實(shí)參提升為32比特長。
  • 受到函數(shù)調(diào)用影響的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS
  • 不受函數(shù)調(diào)用影響的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DS
  • RET指令從函數(shù)被調(diào)用者返回到調(diào)用者(實(shí)質(zhì)上是讀取寄存器EBP所指的線程棧之處保存的函數(shù)返回地址并加載到IP寄存器)
  • Visual C++規(guī)定函數(shù)返回值如果是POD值且長度如果不超過32比特,用寄存器EAX傳遞;長度在33-64比特范圍內(nèi),用寄存器EAX:EDX傳遞;長度超過64比特或者非POD值,則調(diào)用者為函數(shù)返回值預(yù)先分配一個(gè)空間,把該空間的地址作為隱式參數(shù)傳遞給被調(diào)函數(shù)。

    GCC的函數(shù)返回值都是由調(diào)用者分配空間,并把該空間的地址作為隱式參數(shù)傳遞給被調(diào)函數(shù),而不使用寄存器EAX。GCC自4.5版本開始,調(diào)用函數(shù)時(shí),堆棧上的數(shù)據(jù)必須以16B對齊(之前的版本只需要4B對齊即可)。

    考慮下面的C代碼片段:

    int callee(int, int, int);int caller(void){register int ret;ret = callee(1, 2, 3);ret += 5;return ret;}

    在x86上, 會產(chǎn)生如下匯編代碼(AT&T 語法):

    .globl callercaller:pushl %ebpmovl %esp,%ebppushl $3pushl $2pushl $1call calleeaddl $12,%espaddl $5,%eaxleaveret

    在函數(shù)返回后,調(diào)用的函數(shù)清理了堆棧。 在cdecl的理解上存在一些不同,尤其是在如何返回值的問題上。結(jié)果,x86程序經(jīng)過不同OS平臺的不同編譯器編譯后,會有不兼容的情況,即使它們使用的都是“cdecl”規(guī)則并且不會使用系統(tǒng)調(diào)用。某些編譯器返回簡單的數(shù)據(jù)結(jié)構(gòu),長度大致占用兩個(gè)寄存器,放在寄存器對EAX:EDX中;大點(diǎn)的結(jié)構(gòu)和類對象需要異常處理器的一些特殊處理(如一個(gè)定義的構(gòu)造函數(shù),析構(gòu)函數(shù)或賦值),存放在內(nèi)存上。為了放置在內(nèi)存上,調(diào)用者需要分配一些內(nèi)存,并且讓一個(gè)指針指向這塊內(nèi)存,這個(gè)指針就作為隱藏的第一個(gè)參數(shù);被調(diào)用者使用這塊內(nèi)存并返回指針----返回時(shí)彈出隱藏的指針。 在Linux/GCC,浮點(diǎn)數(shù)值通過x87偽棧被推入堆棧。像這樣:

    sub esp, 8 ; 給double值一點(diǎn)空間fld [ebp + x] ; 加載double值到浮點(diǎn)堆棧上fstp [esp] ; 推入堆棧call functadd esp, 8

    使用這種方法確保能以正確的格式推入堆棧。 cdecl調(diào)用約定通常作為x86 C編譯器的默認(rèn)調(diào)用規(guī)則,許多編譯器也提供了自動切換調(diào)用約定的選項(xiàng)。如果需要手動指定調(diào)用規(guī)則為cdecl,編譯器可能會支持如下語法:

    return_type _cdecl funct();

    其中_cdecl修飾符需要在函數(shù)原型中給出,在函數(shù)聲明中會覆蓋掉其他的設(shè)置。

    syscall

    與cdecl類似,參數(shù)被從右到左推入堆棧中。EAX, ECX和EDX不會保留值。參數(shù)列表的大小被放置在AL寄存器中(?)。 syscall是32位OS/2 API的標(biāo)準(zhǔn)。

    optlink

    參數(shù)也是從右到左被推入堆棧。從最左邊開始的三個(gè)字符變元會被放置在EAX, EDX和ECX中,最多四個(gè)浮點(diǎn)變元會被傳入ST(0)到ST(3)中----雖然這四個(gè)參數(shù)的空間也會在參數(shù)列表的棧上保留。函數(shù)的返回值在EAX或ST(0)中。保留的寄存器有EBP, EBX, ESI和EDI。 optlink在IBM VisualAge編譯器中被使用。

    被調(diào)用者清理

    如果被調(diào)用者要清理?xiàng)I系膮?shù),需要在編譯階段知道棧上有多少字節(jié)要處理。因此,此類的調(diào)用約定并不能兼容于可變參數(shù)列表,如printf()。然而,這種調(diào)用約定也許會更有效率,因?yàn)樾枰舛褩5拇a不要在每次調(diào)用時(shí)都生成一遍。 使用此規(guī)則的函數(shù)容易在asm代碼被認(rèn)出,因?yàn)樗鼈儠诜祷厍敖舛褩!86 ret指令允許一個(gè)可選的16位參數(shù)說明棧字節(jié)數(shù),用來在返回給調(diào)用者之前解堆棧。代碼類似如下:

    ret 12

    pascal

    基于Pascal語言的調(diào)用約定,參數(shù)從左至右入棧(與cdecl相反)。被調(diào)用者負(fù)責(zé)在返回前清理堆棧。 此調(diào)用約定常見在如下16-bit 平臺的編譯器:OS/2 1.x,微軟Windows 3.x,以及Borland Delphi版本1.x。

    register

    Borland fastcall的別名。

    stdcall

    stdcall是由微軟創(chuàng)建的調(diào)用約定,是Windows API的標(biāo)準(zhǔn)調(diào)用約定。非微軟的編譯器并不總是支持該調(diào)用協(xié)議。GCC編譯器如下使用:

    int __attribute__((__stdcall__ )) func()

    stdcall是Pascal調(diào)用約定與cdecl調(diào)用約定的折衷:被調(diào)用者負(fù)責(zé)清理線程棧,參數(shù)從右往左入棧。其他各方面基本與cdecl相同。但是編譯后的函數(shù)名后綴以符號"@",后跟傳遞的函數(shù)參數(shù)所占的棧空間的字節(jié)長度。寄存器EAX, ECX和EDX被指定在函數(shù)中使用,返回值放置在EAX中。stdcall對于微軟Win32 API和Open Watcom C++是標(biāo)準(zhǔn)。

    微軟的編譯工具規(guī)定:PASCAL, WINAPI, APIENTRY, FORTRAN, CALLBACK, STDCALL, __far __pascal, __fortran, __stdcall均是指此種調(diào)用約定。

    fastcall

    此約定還未被標(biāo)準(zhǔn)化,不同編譯器的實(shí)現(xiàn)也不一致。

    Microsoft/GCC fastcall

    Microsoft或GCC的__fastcall約定(也即__msfastcall)把第一個(gè)(從左至右)不超過32比特的參數(shù)通過寄存器ECX/CX/CL傳遞,第二個(gè)不超過32比特的參數(shù)通過寄存器EDX/DX/DL,其他參數(shù)按照自右到左順序壓棧傳遞。

    Borland fastcall

    從左至右,傳入三個(gè)參數(shù)至EAX, EDX和ECX中。剩下的參數(shù)推入棧,也是從左至右。 在32位編譯器Embarcadero Delphi中,這是缺省調(diào)用約定,在編譯器中以register形式為人知。 在i386上的某些版本Linux也使用了此約定。

    調(diào)用者或被調(diào)用者清理

    thiscall

    在調(diào)用C++非靜態(tài)成員函數(shù)時(shí)使用此約定。基于所使用的編譯器和函數(shù)是否使用可變參數(shù),有兩個(gè)主流版本的thiscall。 對于GCC編譯器,thiscall幾乎與cdecl等同:調(diào)用者清理堆棧,參數(shù)從右到左傳遞。差別在于this指針,thiscall會在最后把this指針推入棧中,即相當(dāng)于在函數(shù)原型中是隱式的左數(shù)第一個(gè)參數(shù)。

    在微軟Visual C++編譯器中,this指針通過ECX寄存器傳遞,其余同cdecl約定。當(dāng)函數(shù)使用可變參數(shù),此時(shí)調(diào)用者負(fù)責(zé)清理堆棧(參考cdecl)。thiscall約定只在微軟Visual C++ 2005及其之后的版本被顯式指定。其他編譯器中,thiscall并不是一個(gè)關(guān)鍵字(反匯編器如IDA使用__thiscall)。

    Intel ABI

    根據(jù)Intel ABI,EAX、EDX及ECX可以自由在過程或函數(shù)中使用,不需要保留。

    x86-64調(diào)用約定

    x86-64調(diào)用約定得益于更多的寄存器可以用來傳參。而且,不兼容的調(diào)用約定也更少了,不過還是有2種主流的規(guī)則。

    微軟x64調(diào)用約定

    微軟x64調(diào)用約定使用RCX, RDX, R8, R9這四個(gè)寄存器傳遞頭四個(gè)整型或指針變量(從左到右),使用XMM0, XMM1, XMM2, XMM3來傳遞浮點(diǎn)變量。其他的參數(shù)直接入棧(從右至左)。整型返回值放置在RAX中,浮點(diǎn)返回值在XMM0中。少于64位的參數(shù)并沒有做零擴(kuò)展,此時(shí)高位充斥著垃圾。 在Windows x64環(huán)境下編譯代碼時(shí),只有一種調(diào)用約定----就是上面描述的約定,也就是說,32位下的各種約定在64位下統(tǒng)一成一種了。 在微軟x64調(diào)用約定中,調(diào)用者的一個(gè)職責(zé)是在調(diào)用函數(shù)之前(無論實(shí)際的傳參使用多大空間),在棧上的函數(shù)返回地址之上(靠近棧頂)分配一個(gè)32字節(jié)的“影子空間”;并且在調(diào)用結(jié)束后從棧上彈掉此空間。影子空間是用來給RCX, RDX, R8和R9提供保存值的空間,即使是對于少于四個(gè)參數(shù)的函數(shù)也要分配這32個(gè)字節(jié)。

    例如, 一個(gè)函數(shù)擁有5個(gè)整型參數(shù),第一個(gè)到第四個(gè)放在寄存器中,第五個(gè)就被推到影子空間之外的棧頂。當(dāng)函數(shù)被調(diào)用,此棧用來組成返回值----影子空間32位+第五個(gè)參數(shù)。

    在x86-64體系下,Visual Studio 2008在XMM6和XMM7中(同樣的有XMM8到XMM15)存儲浮點(diǎn)數(shù)。結(jié)果對于用戶寫的匯編語言例程,必須保存XMM6和XMM7(x86不用保存這兩個(gè)寄存器),這也就是說,在x86和x86-64之間移植匯編例程時(shí),需要注意在函數(shù)調(diào)用之前/之后,要保存/恢復(fù)XMM6和XMM7。

    System V AMD64 ABI

    此約定主要在Solaris,GNU/Linux,FreeBSD和其他非微軟OS上使用。頭六個(gè)整型參數(shù)放在寄存器RDI, RSI, RDX, RCX, R8和R9上;同時(shí)XMM0到XMM7用來放置浮點(diǎn)變元。對于系統(tǒng)調(diào)用,R10用來替代RCX。同微軟x64約定一樣,其他額外的參數(shù)推入棧,返回值保存在RAX中。 與微軟不同的是,不需要提供影子空間。在函數(shù)入口,返回值與棧上第七個(gè)整型參數(shù)相鄰。

    以上內(nèi)容來源中文維基:https://zh.wikipedia.org/zh-hans/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A

    ?

    我們知道函數(shù)由以下幾部分構(gòu)成:返回值類型 函數(shù)名(參數(shù)列表),如:?
    【code1】

    void function(); int add(int a, int b);

    以上是大家所熟知的構(gòu)成部分,其實(shí)函數(shù)的構(gòu)成還有一部分,那就是調(diào)用約定。如下:?
    【code2】

    void __cdecl function(); int __stdcall add(int a, int b);

    上面的__cdecl和__stdcall就是調(diào)用約定,其中__cdecl是C和C++默認(rèn)的調(diào)用約定,所以通常我們的代碼都如 【code1】中那樣定義,編譯器默認(rèn)會為我們使用__cdecl調(diào)用約定。常見的調(diào)用約定有__cdecl、__stdcall、fastcall,應(yīng)用最廣泛的是__cdecl和__stdcall,下面我們會詳細(xì)進(jìn)行講述。。還有一些不常見的,如 __pascal、__thiscall、__vectorcall。

    聲明和定義處調(diào)用約定必須要相同

    在VC++中,調(diào)用約定是函數(shù)類型的一部分,因此函數(shù)的聲明和定義處調(diào)用約定要相同,不能只在聲明處有調(diào)用約定,而定義處沒有或與聲明不同。如下:?
    【code3】 錯誤的使用一:

    int __stdcall add(int a, int b); int add(int a, int b) {return a + b; }

    報(bào)錯:

    error C2373: ‘a(chǎn)dd’: redefinition; different type modifiers?
    error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’

    補(bǔ)充:

    int __cdecl add(int a, int b); int add(int a, int b) {return a + b; }

    以上就沒問題,因?yàn)槟J(rèn)是__cdecl。

    【code4】 錯誤的使用二:

    int add(int a, int b); int __stdcall add(int a, int b) {return a + b; }

    報(bào)錯:

    error C2373: ‘a(chǎn)dd’: redefinition; different type modifiers?
    error C2440: ‘initializing’: cannot convert from ‘int (__cdecl *)(int,int)’ to ‘int’

    【code5】 錯誤的使用三:

    int __stdcall add(int a, int b); int __cdecl add(int a, int b) {return a + b; }

    報(bào)錯:

    error C2373: ‘a(chǎn)dd’: redefinition; different type modifiers?
    error C2440: ‘initializing’: cannot convert from ‘int (__stdcall *)(int,int)’ to ‘int’

    【code6】 正確的用法:

    int __stdcall add(int a, int b); int __stdcall add(int a, int b) {return a + b; }

    函數(shù)的調(diào)用過程

    要深入理解函數(shù)調(diào)用約定,你須要了解函數(shù)的調(diào)用過程和調(diào)用細(xì)節(jié)。?
    假設(shè)函數(shù)A調(diào)用函數(shù)B,我們稱A函數(shù)為”調(diào)用者”,B函數(shù)為“被調(diào)用者”。如下面的代碼,ShowResult為調(diào)用者,add為被調(diào)用者。

    int add(int a, int b) {return a + b; }void ShowResult() {std::cout << add(5, 10) << std::endl; }

    函數(shù)調(diào)用過程可以這么描述:?
    (1)先將調(diào)用者(A)的堆棧的基址(ebp)入棧,以保存之前任務(wù)的信息。?
    (2)然后將調(diào)用者(A)的棧頂指針(esp)的值賦給ebp,作為新的基址(即被調(diào)用者B的棧底)。?
    (3)然后在這個(gè)基址(被調(diào)用者B的棧底)上開辟(一般用sub指令)相應(yīng)的空間用作被調(diào)用者B的棧空間。?
    (4)函數(shù)B返回后,從當(dāng)前棧幀的ebp即恢復(fù)為調(diào)用者A的棧頂(esp),使棧頂恢復(fù)函數(shù)B被調(diào)用前的位置;然后調(diào)用者A再從恢復(fù)后的棧頂可彈出之前的ebp值(可以這么做是因?yàn)檫@個(gè)值在函數(shù)調(diào)用前一步被壓入堆棧)。這樣,ebp和esp就都恢復(fù)了調(diào)用函數(shù)B前的位置,也就是棧恢復(fù)函數(shù)B調(diào)用前的狀態(tài)。?
    這個(gè)過程在AT&T匯編中通過兩條指令完成,即:?

    leaveret這兩條指令更直白點(diǎn)就相當(dāng)于:mov %ebp , %esppop %ebp

    ?

    __cdecl的特點(diǎn)

    __cdecl 是 C Declaration 的縮寫,表示 C 和 C++ 默認(rèn)的函數(shù)調(diào)用約定。是C/C++和MFCX的默認(rèn)調(diào)用約定。

    • 按從右至左的順序壓參數(shù)入棧、。
    • 由調(diào)用者把參數(shù)彈出棧。切記:對于傳送參數(shù)的內(nèi)存棧是由調(diào)用者來維護(hù)的,返回值在EAX中。因此對于像printf這樣可變參數(shù)的函數(shù)必須用這種約定。
    • 編譯器在編譯的時(shí)候?qū)@種調(diào)用規(guī)則的函數(shù)生成修飾名的時(shí)候,在輸出函數(shù)名前加上一個(gè)下劃線前綴,格式為_function。如函數(shù)int add(int a, int b)的修飾名是_add。

    (1).為了驗(yàn)證參數(shù)是從右至左的順序壓棧的,我們可以看下面這段代碼,Debug進(jìn)行單步調(diào)試,可以看到我們的調(diào)用棧會先進(jìn)入GetC(),再進(jìn)入GetB(),最后進(jìn)入GetA()。?

    (2).第二點(diǎn)“調(diào)用者把參數(shù)彈出棧”,這是編譯器的工作,暫時(shí)沒辦法驗(yàn)證。要深入了解這部分,需要學(xué)習(xí)匯編語言相關(guān)的知識。

    (3).函數(shù)的修飾名,這個(gè)可以通過對編譯出的dll使用VS的”dumpbin /exports?ProjectName.dll”命令進(jìn)行查看(后面章節(jié)會進(jìn)行詳細(xì)介紹),或直接打開.obj文件查找對應(yīng)的方法名(如搜索add)。

    從代碼和程序調(diào)試的層面考慮,參數(shù)的壓棧順序和棧的清理我們都不用太觀注,因?yàn)檫@是編譯器的決定的,我們改變不了。但第三點(diǎn)卻常常困擾我們,因?yàn)槿绻慌宄@點(diǎn),在多個(gè)庫之間(如dll、lib、exe)相互調(diào)用、依賴時(shí)常常出出現(xiàn)莫名其妙的錯誤。這個(gè)我在后面章節(jié)會進(jìn)行詳細(xì)介紹。

    __stdcall的特點(diǎn)

    __stdcall是Standard Call的縮寫,是C++的標(biāo)準(zhǔn)調(diào)用方式,當(dāng)然這是微軟定義的標(biāo)準(zhǔn),__stdcall通常用于Win32 API中(可查看WINAPI的定義)。?? microsoft的vc默認(rèn)的是__cdecl方式,而windows API則是__stdcall,如果用vc開發(fā)dll給其他語言用,則應(yīng)該指定__stdcall方式。堆棧由誰清除這個(gè)很重要,如果是要寫匯編函數(shù)給C調(diào)用,一定要小心堆棧的清除工作,如果是__cdecl方式的函數(shù),則函數(shù)本身(如果不用匯編寫)則不需要關(guān)心保存參數(shù)的堆棧的清除,但是如果是__stdcall的規(guī)則,一定要在函數(shù)退出(ret)前恢復(fù)堆棧。

    • 按從右至左的順序壓參數(shù)入棧。
    • 由被調(diào)用者把參數(shù)彈出棧。切記:函數(shù)自己在退出時(shí)清空堆棧,返回值在EAX中。
    • __stdcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)下劃線前綴,后面加上一個(gè)“@”符號和其參數(shù)的字節(jié)數(shù),格式為_function@number。如函數(shù)int sub(int a, int b)的修飾名是_sub@8。

    __fastcall的特點(diǎn)

    __fastcall調(diào)用的主要特點(diǎn)就是快,因?yàn)樗峭ㄟ^寄存器來傳送參數(shù)的。

    • 實(shí)際上__fastcall用ECX和EDX傳送前兩個(gè)DWORD或更小的參數(shù),剩下的參數(shù)仍自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的內(nèi)存棧。
    • __fastcall調(diào)用約定在輸出函數(shù)名前加上一個(gè)“@”符號,后面也是一個(gè)“@”符號和其參數(shù)的字節(jié)數(shù),格式為@function@number,如double multi(double a, double b)的修飾名是@multi@16。
    • __fastcall和__stdcall很象,唯一差別就是頭兩個(gè)參數(shù)通過寄存器傳送。注意通過寄存器傳送的兩個(gè)參數(shù)是從左向右的,即第1個(gè)參數(shù)進(jìn)ECX,第2個(gè)進(jìn)EDX,其他參數(shù)是從右向左的入棧,返回仍然通過EAX。

    __thiscall

    __thiscall是C++類成員函數(shù)缺省的調(diào)用約定,但它沒有顯示的聲明形式。因?yàn)樵贑++類中,成員函數(shù)調(diào)用還有一個(gè)this指針參數(shù),因此必須特殊處理,thiscall調(diào)用約定的特點(diǎn):

    • 參數(shù)入棧:參數(shù)從右向左入棧
    • this指針入棧:如果參數(shù)個(gè)數(shù)確定,this指針通過ecx傳遞給被調(diào)用者;如果參數(shù)個(gè)數(shù)不確定,this指針在所有參數(shù)壓棧后被壓入棧。
    • 棧恢復(fù):對參數(shù)個(gè)數(shù)不定的,調(diào)用者清理?xiàng)?#xff0c;否則函數(shù)自己清理?xiàng)!?/li>

    總結(jié)

    這里主要總結(jié)一下_cdecl、_stdcall、__fastcall三者之間的區(qū)別:

    要點(diǎn)__cdecl__stdcall__fastcall
    參數(shù)傳遞方式右->左右->左左邊開始的兩個(gè)不大于4字節(jié)(DWORD)的參數(shù)分別放在ECX和EDX寄存器,其余的參數(shù)自右向左壓棧傳送
    清理?xiàng)7?/td>調(diào)用者清理被調(diào)用函數(shù)清理被調(diào)用函數(shù)清理
    適用場合C/C++、MFC的默認(rèn)方式; 可變參數(shù)的時(shí)候使用;Win API要求速度快
    C編譯修飾約定_functionname_functionname@number@functionname@number

    以上內(nèi)容參考:https://blog.csdn.net/luoweifu/article/details/52425733#commentBox

    _cdecl 是C Declaration的縮寫,表示C語言默認(rèn)的函數(shù)調(diào)用方法:所有參數(shù)從右到左依次入棧,這些參數(shù)由調(diào)用者清除,稱為手動清棧。被調(diào)用函數(shù)無需要求調(diào)用者傳遞多少參數(shù),調(diào)用者傳遞過多或者過少的參數(shù),甚至完全不同的參數(shù)都不會產(chǎn)生編譯階段的錯誤。

    _stdcall 是Standard Call的縮寫,是C++的標(biāo)準(zhǔn)調(diào)用方式:所有參數(shù)從右到左依次入棧,如果是調(diào)用類成員的話,最后一個(gè)入棧的是this指針。這些堆棧中的參數(shù)由被調(diào)用的函數(shù)在返回后清除,使用的指令是 retn X,X表示參數(shù)占用的字節(jié)數(shù),CPU在ret之后自動彈出X個(gè)字節(jié)的堆棧空間。稱為自動清棧。函數(shù)在編譯的時(shí)候就必須確定參數(shù)個(gè)數(shù),并且調(diào)用者必須嚴(yán)格的控制參數(shù)的生成,不能多,不能少,否則返回后會出錯。

    PASCAL 是Pascal語言的函數(shù)調(diào)用方式,也可以在C/C++中使用,參數(shù)壓棧順序與前兩者相反。返回時(shí)的清棧方式忘記了。。。

    _fastcall 是編譯器指定的快速調(diào)用方式。由于大多數(shù)的函數(shù)參數(shù)個(gè)數(shù)很少,使用堆棧傳遞比較費(fèi)時(shí)。因此_fastcall通常規(guī)定將前兩個(gè)(或若干個(gè))參數(shù)由寄存器傳遞,其余參數(shù)還是通過堆棧傳遞。不同編譯器編譯的程序規(guī)定的寄存器不同。返回方式和_stdcall相當(dāng)。

    _thiscall 是為了解決類成員調(diào)用中this指針傳遞而規(guī)定的。_thiscall要求把this指針放在特定寄存器中,該寄存器由編譯器決定。VC使用ecx,Borland的C++編譯器使用eax。返回方式和_stdcall相當(dāng)。

    _fastcall 和 _thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對象接口都定義為_stdcall調(diào)用方式。

    C中不加說明默認(rèn)函數(shù)為_cdecl方式(C中也只能用這種方式),C++也一樣,但是默認(rèn)的調(diào)用方式可以在IDE環(huán)境中設(shè)置。

    帶有可變參數(shù)的函數(shù)必須且只能使用_cdecl方式,例如下面的函數(shù):
    int printf(char * fmtStr, ...);
    int scanf(char * fmtStr, ...);

    這兩個(gè)關(guān)鍵字看起來似乎很少和我們打交道,但是看了下面的定義(來自windef.h
    ),你一定會覺得驚訝:
    ???? #define CALLBACK???? __stdcall
    ???? #define WINAPI?????? __stdcall
    ???? #define WINAPIV????? __cdecl
    ???? #define APIENTRY???? WINAPI
    ???? #define APIPRIVATE?? __stdcall
    ???? #define PASCAL?????? __stdcall
    ???? #define cdecl _cdecl
    ???? #ifndef CDECL
    ???? #define CDECL _cdecl
    ???? #endif
    ????幾乎我們寫的每一個(gè)WINDOWS API函數(shù)都是__stdcall類型的,為什么??
    ???? 首先,我們談一下兩者之間的區(qū)別:
    ?????? WINDOWS的函數(shù)調(diào)用時(shí)需要用到棧(STACK,一種先入后出的存儲結(jié)構(gòu))。當(dāng)函數(shù)調(diào)用完成后,棧需要清除,這里就是問題的關(guān)鍵,如何清除??
    ?????? 如果我們的函數(shù)使用了_cdecl,那么棧的清除工作是由調(diào)用者,用COM的術(shù)語來講就是客戶來完成的。這樣帶來了一個(gè)棘手的問題,不同的編譯器產(chǎn)生棧的方式不盡相同,那么調(diào)用者能否正常的完成清除工作呢?答案是不能。
    ?????? 如果使用__stdcall,上面的問題就解決了,函數(shù)自己解決清除工作。所以,在跨(開發(fā))平臺的調(diào)用中,我們都使用__stdcall(雖然有時(shí)是以WINAPI的樣子出現(xiàn))。
    ? ? ? ?那么為什么還需要_cdecl呢?當(dāng)我們遇到這樣的函數(shù)如fprintf()它的參數(shù)是可變的,不定長的,被調(diào)用者事先無法知道參數(shù)的長度,事后的清除工作也無法正常的進(jìn)行,因此,這種情況我們只能使用_cdecl。
    ?????? 到這里我們有一個(gè)結(jié)論,如果你的程序中沒有涉及可變參數(shù),最好使用__stdcall關(guān)鍵字

    總結(jié)

    以上是生活随笔為你收集整理的【转】调用约定__cdecl、__stdcall和__fastcall的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。