2009-08-20 156 views
48

這幾天我遇到過很多__stdcall
在我看來,MSDN並沒有很清楚地解釋它的真正含義,
什麼時候以及爲什麼要使用它,如果有的話。__stdcall的含義和用法是什麼?

如果有人提供解釋,我會很感激,最好有一個或兩個例子。

感謝

回答

45

C/C++中的所有函數有一個特定的調用約定。調用約定的要點是確定調用者和被調用者之間的數據傳遞方式,以及誰負責清理調用堆棧等操作。

在Windows上最流行的調用約定

  • STDCALL
  • CDECL
  • clrcall
  • FASTCALL
  • thiscall

添加此說明符函數聲明本質上是告訴編譯器,你希望這個特定的函數有這個特定的調用約定。

調用約定在這裏

記錄雷蒙德陳也做了不同的調用約定(5份),從這裏開始,歷史悠久系列。

1

這是WinAPI的功能需要妥善稱爲調用約定。調用約定是關於如何將參數傳遞給函數以及如何從函數傳遞返回值的一組規則。

如果調用者和被調用代碼使用不同的約定,則會遇到未定義的行爲(如such a strange-looking crash)。

C++編譯器默認不使用__stdcall - 它們使用其他約定。所以爲了從C++調用WinAPI函數,你需要指定他們使用__stdcall--這通常是在Windoes SDK頭文件中完成的,你也可以在聲明函數指針時進行。

3

它指定了函數的調用約定。一個調用約定是一組規則,它是如何將參數傳遞給一個函數的:按哪個順序,每個地址或每個副本,誰來清理參數(調用者或被調用者)等。

5

不幸的是,對於何時使用它,什麼時候不是沒有簡單的答案。

__stdcall表示函數的參數從第一個到最後一個被壓入堆棧。這與__cdecl相反,這意味着參數從最後一個推到第一個,而__fastcall將前四個(我認爲)參數放在寄存器中,剩下的放在堆棧中。

你只需要知道被調用者的期望,或者如果你正在編寫一個庫,你的調用者可能期望什麼,並確保你記錄你選擇的約定。

+2

'__stdcall'和'__cdecl'只負責不同的收益(和裝飾)後清理。兩個參數的傳遞是相同的(從右到左)。你所描述的是Pascal調用約定。 – a3f 2015-05-28 06:26:40

6

__stdcall是一種調用約定:一種確定參數如何傳遞給函數(在堆棧或寄存器中)以及誰負責在函數返回後清除(調用者或被調用者)的方法。

Raymond Chen寫了一個blog about the major x86 calling conventions,還有一個不錯的CodeProject article

大多數情況下,您不必擔心它們。唯一的情況是,如果您調用的庫函數使用的不是默認值 - 否則編譯器會生成錯誤的代碼,您的程序可能會崩潰。

2

__stdcall表示調用約定(有關詳細信息,請參閱this PDF)。這意味着它指定函數參數如何從堆棧中被推送和彈出,以及誰負責。

__stdcall只是幾個調用約定中的一個,並在整個WINAPI中使用。如果您提供函數指針作爲其中一些函數的回調函數,則必須使用它。一般來說,除了上面提到的情況(爲第三方代碼提供回調)之外,您不需要在代碼中指定任何特定的調用約定,而只需使用編譯器的默認值即可。

1

簡單地說,當你調用函數,它被裝入堆棧/寄存器。 __stdcall是一個約定/方式(正確的參數第一,然後左參數...),__decl是另一個約定,用於加載堆棧或寄存器上的函數。

如果您使用它們,則指示計算機在鏈接過程中使用該特定方式加載/卸載該函數,因此不會出現不匹配/崩潰。

否則函數調用者和函數調用者可能會使用不同的約定導致程序崩潰。

33

傳統上,調用者將一些參數推入堆棧,調用函數,然後彈出堆棧以清理那些推入的參數,從而進行C函數調用。

/* example of __cdecl */ 
push arg1 
push arg2 
push arg3 
call function 
add sp,12 // effectively "pop; pop; pop" 

注意:默認約定 - 如上所示 - 被稱爲__cdecl。

另一個最受歡迎的約定是__stdcall。在這個參數中,參數再次被調用者推送,但堆棧被調用者清理。它是Win32 API函數的標準約定(由WINAPI宏定義),它有時也被稱爲「Pascal」調用約定。

/* example of __stdcall */ 
push arg1 
push arg2 
push arg3 
call function // no stack cleanup - callee does this 

這看起來像一個小的技術細節,但如果有關於如何堆棧的主叫用戶和被叫之間的管理分歧,堆棧會的方式,不太可能恢復被破壞。 由於__stdcall沒有進行堆棧清理,因此執行此任務的(非常小的)代碼只能在一個地方找到,而不是像每個調用程序中的__cdecl中那樣複製。這使得代碼稍微小一些,儘管只有大型程序才能看到尺寸的影響。

如printf參數可變型函數()幾乎是不可能得到正確的使用__stdcall,因爲只有真正來電者知道有多少爭論,以清除它們獲得通過。被叫方可以(通過查看格式字符串說,)提出一些很好的猜測,但堆棧清理必須由函數,而不是調用約定機制本身的實際邏輯來決定。因此,只有__cdecl支持可變參數函數,以便調用者可以進行清理。

鏈接器符號名稱修飾: 正如上面的要點所述,使用「錯誤」約定調用函數可能是災難性的,因此Microsoft有一種機制可以避免發生這種情況。它運作良好,但如果不知道原因是什麼,它會令人發狂。 他們選擇通過編碼調用約定與額外字符(這通常被稱爲「裝飾」)的低級別的功能名稱來解決這個問題,而這些都被視爲鏈接器無關的名字。默認調用約定__cdecl,但每個人都可以明確地與/ G請求?參數給編譯器。

__cdecl(CL /釓...)

所有此類型的函數名前面有下劃線,而且由於呼叫者負責堆棧設置和堆棧清理參數的數量並不重要。這是可能的主叫方和被叫方是超過實際傳遞參數的數量感到困惑,但至少堆棧規則正確維護。

__stdcall(CL/GZ ...)

這些函數名的前綴爲下劃線並用@所附加的傳遞的參數的字節數。通過這種機制,它不是可調用的「錯誤」類型的函數,甚至數錯誤的參數。

__fastcall(CL /石墨...)

這些函數名開始的@符號和後綴與@parameter計數,很像__stdcall。

實例:

Declaration      -----------------------> decorated name 


void __cdecl foo(void);   -----------------------> _foo 

void __cdecl foo(int a);   -----------------------> _foo 

void __cdecl foo(int a, int b); -----------------------> _foo 

void __stdcall foo(void);   -----------------------> [email protected] 

void __stdcall foo(int a);   -----------------------> [email protected] 

void __stdcall foo(int a, int b); -----------------------> [email protected] 

void __fastcall foo(void);   -----------------------> @[email protected] 

void __fastcall foo(int a);  -----------------------> @[email protected] 

void __fastcall foo(int a, int b); -----------------------> @[email protected] 
1

__stdcall是用於該函數的調用約定。這會告訴編譯器適用於設置堆棧,推送參數和獲取返回值的規則。還有一些其他的調用約定的像__cdecl__thiscall__fastcall__naked

__stdcall是Win32系統調用的標準調用約定。

更多細節可以在Wikipedia找到。