天天看點

C/C++調用約定

                             C/C++函數調用約定

在程式設計中,一個函數完整的執行需要經過編譯連結等多個過程,而在每個過程中編譯器都需要為程式提供不同的服務,那麼一個函數的調用執行到底需要幾個過程呢?下面我們先通過一個函數棧幀的建立看看。

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

#include<stdlib.h>

int Add(int left, int right)

{

return left + right;

}

int main()

{

int a, b;

int ret = 0;

scanf("%d%d", &a, &b);

ret = Add(a, b);

system("system");

return 0;

}

   在這個程式中,我們來通過Add()函數的内部彙編代碼來看看整個函數的執行過程:

C/C++調用約定

下面我們用語言描述一下整個過程:

1. 為函數開辟空間

2. 初始化已開辟空間

3. 把函數參數壓棧 

4. 執行函數 

5. 處理函數傳回值 

6. 對于第3步中壓棧的那些寄存器,恢複它們原來的值 

7. 根據不同的調用約定,清除第1步中壓棧的參數,然後傳回,或者先傳回然後清除。 

    可以看到第6步是第3步的逆操作,而第7步是第1,2步的逆操作,在第3步中對函數的參數進行壓棧,那麼當參數個數多于一個時,編譯器會按照什麼順序把參數壓入棧的?而在第6、7步中又是怎麼把堆棧恢複原裝?這就引出了我們今天要讨論的題目——調用約定。

首先我們先來看看常用的幾種調用約定:

在C語言中有: __cdecl、__stdcall、__fastcall、naked、__pascal

在C++中有: __cdecl、__stdcall、__fastcall、naked、__pascal、_thiscall

VC 中預設調用是 __cdecl 方式,Windows API 使用 __stdcall 調用方式,在 DLL 導出函數中,為了跟Windows API 保持一緻,建議使用 __stdcall 方式。

 在VC中,可以設定預設的調用約定,設定路徑為:Project à Properties à Configuration Properties à   C/C++ à   Advanced à  call conversion

  下面我們就來詳細介紹一下這六種調用約定:

    1、__cdecl

__cdecl調用約定又稱為 C 調用約定,是 C/C++ 語言預設的調用約定。參數按照從右至左的方式入棧,函數本身不清理棧,此工作由調用者負責,傳回值在EAX中。由于由調用者清理棧,是以允許可變參數函數存在,如int sprintf(char* buffer,const char* format,...);。

2、__stdcall

__stdcall 很多時候被稱為 pascal 調用約定。pascal 語言是早期很常見的一種教學用計算機程式設計語言,其文法嚴謹。參數按照從右至左的方式入棧,函數自身清理堆棧,傳回值在EAX中。

3、__fastcall

顧名思義,__fastcall 的特點就是快,因為它通過 CPU 寄存器來傳遞參數。他用 eax 和 edx 傳送前兩個雙字(DWORD)或更小的參數,剩下的參數按照從右至左的方式入棧,函數自身清理堆棧,傳回值在 EAX 中。

4、naked

naked 是一個很少見的調用約定,一般不建議使用。編譯器不會給這種函數增加初始化和清理代碼,更特殊的是,你不能用return傳回傳回值,隻能用插入彙編傳回結果,此調用約定必須跟 __declspec 同時使用。例如定義一個求和程式,如__declspec(naked) int  add(int a,int b);。

5、__pascal

這是 pascal 語言的調用約定,跟 __stdcall 一樣,參數按照從右至左的方式入棧,函數自身清理堆棧,傳回值在eax中。VC 中已經廢棄了這種調用方式,是以在寫 VC 程式時,建議使用 __stdcall 代替。

6、__thiscall

這是 C++ 語言特有的一種調用方式,用于類成員函數的調用約定。如果參數确定,this 指針存放于 ecx 寄存器,函數自身清理堆棧;如果參數不确定,this指針在所有參數入棧後再入棧,調用者清理棧。__thiscall 不是關鍵字,程式員不能使用。參數按照從右至左的方式入棧。