日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言函數調用約定和返回值詳情_C 語言

作者:BugMaker-shen ? 更新時間: 2022-09-07 編程語言

?一、函數調用約定

  • _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訪問該臨時量的地址

原文鏈接:https://blog.csdn.net/qq_42500831/article/details/124432347

欄目分類
最近更新