首頁 > 軟體

C語言函數呼叫約定和返回值詳情

2022-07-14 22:03:45

 一、函數呼叫約定

  • _cdecl:C呼叫約定
  • _stdcall:Windows標準的呼叫約定
  • _fastcall:快速呼叫約定
  • _thiscall:C++的成員函數呼叫約定

以上的函數呼叫約定入參都是從右向左,只有PASCAL從左向右

函數呼叫約定不同,會影響函數生成的符號名,函數入參順序,形參記憶體的清理者

1. 影響函數生成的符號名

在一個檔案中寫_cdecl的函數宣告:

在另一個檔案中寫_stdcall的函數定義:

我們編譯連結一下:

連結器找不到__cdecl sum這個函數呼叫的定義,將宣告的地方改成__stdcall就可以連結成功

2. 影響形參記憶體的釋放者

_stdcall

形參記憶體還是由呼叫方開闢

ret表示把棧頂元素的值(呼叫處下一條指令的地址)賦給PC暫存器,並出棧棧頂元素(修改esp)
ret 8表示在ret操作的基礎上,執行執行指令add esp, 8

_fastcall

可以看到在_fastcall呼叫約定中,call指令前面並沒有push操作,而是通過暫存器把實參傳遞到形參(沒有壓棧出棧,速度很快),實參在呼叫方棧幀上,形參在被呼叫方棧幀上

在_fastcall呼叫約定中,最多隻能通過暫存器將最左邊8位元組的實參帶給形參,多於8位元組的實參還是通過push的方式帶給呼叫方的形參

我們給sum傳入三個引數,看看是誰釋放形參記憶體 :

這就很清楚了,一共3個引數,左邊的2個引數通過暫存器傳遞不需要清理記憶體,只有一個形參記憶體需要釋放,所以顯示ret 4

對於sum函數的第一個區域性變數temp,在_cdecl和_stdcall中都是通過ebp-4存取的,形參是通過ebp正向偏移存取,因為形參記憶體在呼叫方的棧幀上

而在_fastcall中是通過暫存器把左邊的8位元組實參帶給sum的形參,並存放在sum函數的棧幀上:

mov edx, dword ptr [ebp-8]
mov ecx, dword ptr [ebp-4]

所以對於sum函數的第一個區域性變數temp,只能通過ebp-0Ch存取

_thiscall

對引數個數不確定的,呼叫者清理堆疊,否則被呼叫者清理堆疊

二、函數的返回值

函數的返回值分為內建型別(char、short、int、long、float、double等)、結構體型別、union、enum等

1. 0 < 返回值 <= 4位元組

通過eax暫存器帶出

2. 4位元組 < 返回值 <= 8位元組

#include <stdio.h>

typedef struct  {
	int a;
	int b;
}Data;

Data sum(Data a, Data b) {
	Data temp = { 0 };
	temp.a = a.a + b.a;
	return temp;
}
int main() {
	Data a = { 10 }; 
	Data b = {20};  
	Data ret = { 0 }; 
	ret = sum(a, b);
	return 0;
}

可以看到,4位元組 < 返回值 <= 8位元組時,通過eax和edx暫存器帶出

3. 返回值 > 8位元組

#include <stdio.h>

typedef struct  {
	int a[20];
}Data;

Data sum(Data a, Data b) {
	Data temp = { 0 };
	temp.a[0] = a.a[0] + b.a[0];
	return temp;
}

int main() {
	Data a = { 10 };
	Data b = {20};
	Data ret = { 0 };
	ret = sum(a, b);
	return 0;
}

壓引數的時候,沒有使用push指令,因為暫存器不夠用,故使用了迴圈拷貝的方法,從實參的空間拷貝到形參的空間

產生臨時量有三個地方:函數呼叫前,函數呼叫時return的地方,函數呼叫完成時。在接收大於8位元組返回值時,是在函數呼叫前產生臨時量,並把臨時量記憶體的地址壓棧,而這個臨時量是用來接收返回值的

我們看到不僅僅壓棧了實參a、b,還壓棧了臨時量的地址,可以把sum函數簡單理解為如下形式:

Data sum(void* tmp_address, Data a, Data b);

我們看一下sum函數中return時的組合指令是如何待會80位元組的返回值的

最後通過eax把臨時量的地址帶出來,呼叫函數就可以通過eax拿到sum函數的返回值了

如果臨時量在函數呼叫前產生,那被呼叫函數返回的時候,肯定是通過ebp+8存取臨時量並寫入返回值。因為ebp指向的空間儲存了呼叫函數的棧底地址,ebp+4指向的空間儲存了call指令下一條指令的地址,ebp+8指向最後一個壓棧的實參,即用於帶出返回值的臨時量的地址

返回方式:

  • 返回值空間在[1,4],用eax暫存器
  • 返回值空間在[5,8],用eax、edx暫存器
  • 返回值空間大於8位元組時,函數呼叫前產生臨時量用於儲存返回值,並把這個臨時量的地址作為最後一個實參壓棧,在被呼叫函數中通過ebp+8存取該臨時量的地址

到此這篇關於C語言函數呼叫約定和返回值詳情的文章就介紹到這了,更多相關C函數呼叫內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com