調用約定

對於常見的指令集,在指令層面沒有所謂的「函數」概念,只有「子程序」概念。子程序是存儲在「主程序」以外的一段指令。子程序經過call指令調用,經過ret指令返回。子程序可使用內存、堆棧和寄存器。一般主程序會傳遞參數給子程序,子程序將執行結果返回給主程序。這些參數和返回值如何傳遞,能夠由開發者決定。不過若是程序中同時使用了高級語言和彙編語言,爲了讓編譯器生成的彙編代碼能夠正確的彙編和鏈接,必須採用一個雙方都遵照的傳遞參數和返回值的方法。這就是調用約定。換言之,調用約定是爲了保證了不一樣函數能夠正確彙編和連接而設計的,主程序和子程序之間傳遞數據的方式。html

從上面的說明能夠看到,調用約定涉及參數和返回值兩部分。早期的高級語言(好比C)只有一個返回值,所以返回值的傳遞也較爲簡單。下表總結了幾個平臺上返回值的傳遞方法:函數

| 平臺 | 整型 | 結構體 | 浮點型 |
| x86 | eax | eax | st(0) |
| x86-64 | rax | rax | xmm0 |
| ARM | R0 | R0 | R0 |
| ARM64 | R0 | R0 | R0 |

調用約定中比較複雜的是參數傳遞方法,其中又以x86平臺的調用約定種類繁多。this

x86參數傳遞 在32位x86系統上,因爲寄存器數量較少,參數主要經過棧傳遞,也產生了比較多的調用方式。MSVC和GCC支持的32位調用約定有:.net

| 關鍵字 | 清理堆棧 | 參數傳遞 |
| __cdecl | caller | 從右向左將參數壓棧。 |
| __clrcall | n/a | 將參數從左向右加入CLR表達式棧。 |
| __stdcall | callee | 從右向左將參數壓棧。 |
| __fastcall | callee | 優先使用寄存器ecx和edx傳遞參數,而後才使用堆棧。 |
| __thiscall | callee | 經過ecx傳遞this指針,其餘參數經過棧傳遞。 |
| __vectorcall | callee | 從右向左傳遞參數,優先使用寄存器ecx和edx,而後才使用堆棧。 |

經過棧傳遞參數時,棧的結構以下:設計

16(%ebp) - third function parameter
12(%ebp) - second function parameter
8(%ebp) - first function parameter
4(%ebp) - old %EIP (the function's "return address")
0(%ebp) - old %EBP (previous function's base pointer)
-4(%ebp) - first local variable
-8(%ebp) - second local variable
-12(%ebp) - third local variable

x86-64參數傳遞 x86-64擁有比較多的寄存器,所以主要經過寄存器傳遞參數,調用約定也較爲統一。MSVC編譯器經過rcx、rdx、r八、r9傳遞前4個參數,其他參數同過棧傳遞,正以下面的例子:指針

void func1(int a, int b, int c, int d, int e);
// a: rcx, b: rdx, c: r8, d: r9, e: stack

若是函數的參數是結構體,編譯器將這個結構體的指針做爲實際參數傳遞。若是參數是浮點數,將經過寄存器xmm0、xmm一、xmm2和xmm3傳遞。若是參數中既有整數又有浮點數,編譯器將按照下面的例子傳遞參數:code

void func2(int a, fouble b, int c, float d);
// a: rcx b: xmm1 c: r8 d: xmm3

若是參數經過棧傳遞,調用者(caller)負責清理堆棧。C++程序的this指針一般做爲第一個參數,經過rcx傳遞。htm

在Linux上,GCC優先經過rdi、rsi、rdx、rcx、r八、r9傳遞參數。對於浮點數參數,GCC使用xmm0-xmm7寄存器。和MSVC同樣,GCC也要求調用者清理堆棧。同時,GCC也將C++的this指針做爲第一個參數。所以在GCC中,this指針經過rdi傳遞。blog

參考資料ip

相關文章
相關標籤/搜索