C函式引數傳遞與返回值傳遞

2021-07-23 03:53:03 字數 3383 閱讀 7596

(1)引數傳遞

__stdcall和__cdecl都是函式呼叫約定關鍵字,先給出這兩者的區別,然後舉例項分析:

__stdcall:引數由右向左壓入堆疊;堆疊由函式本身清理。

__cdecl:引數也是由右向左壓入堆疊;但堆疊由呼叫者清理。

另外,這兩者在同一名字修飾約定下,編譯過後變數和函式的名字也不一樣,具體見另一博文:名字修飾約定extern "c"與extern "c++"**

下面給出例項分析:

[cpp]view plain

copy

#include "stdio.h"

#include 

#include 

#include 

using

namespace

std;  

int__stdcall func_stdcall(

intnparam1, 

intnparam2)  

int__cdecl func_cdecl(

intnparam1, 

intnparam2)  

intmain()    

以上**在xp + vc++6.0 sp6環境下編譯,編譯後的彙編**如下:

首先要明確上圖彙編**中幾個指令的作用:

1.call:將call下一條指令的eip壓入堆疊,然後跳到@後標號位址處執行;eip相當於儲存著當前程式的計數器值。

2.ret:將堆疊的當前資料彈出給eip,然後繼續執行;

3.ret n:n表示乙個整數,將堆疊的當前資料彈出給eip,再將esp的值加上n,然後繼續執行。

我們再看彙編**,呼叫func_stdcall和func_cdecl時,都是由呼叫者(main函式)將引數壓入堆疊,注

意位址0x00401127、0x00401129和0x00401133、0x00401135都是先壓入2,再壓入1,這個順序就是函式引數由右向左的順序。

再注意位址0x0040110f,這是呼叫func_stdcall時的出口指令,"ret 8"先把eip的值彈出,然後再將esp的值加8,相當於執行兩次出棧的操作。因為編譯環境是32位的,呼叫func_stdcall時壓入的2和1,其實是壓入的兩個32位整數值,剛好佔8個位元組。然後再繼續執行eip處的指令,此時eip的值應為0x00401130,為call指令的下一條指令,這條指令是將返回的值賦給變數a。可見,堆疊的清理是由func_stdcall內部處理的,外部呼叫者並不處理。

然後再來看看__cdecl修飾的func_cdecl,注意位址0x0040111b,只有乙個指令「ret」,只將堆疊當前的值彈出給eip,然後繼續執行。但是在呼叫前已經壓入了兩個32位的整數值,堆疊還沒有被清理。我們再來看看繼續執行的指令,位址0x0040113c處的指令為繼續執行的指令,指令為「add esp,8「,這個很好理解了,直接將esp的值加上8,也相當於執行兩次出棧操作。但這是由呼叫者(main引數)進行的,因此堆疊是由呼叫者進行清理的。

__stdcall通常用於windows api中,可見如下**:

[cpp]view plain

copy

#define callback    __stdcall

#define winapi      __stdcall

#define winapiv     __cdecl

#define apientry    winapi

#define apiprivate  __stdcall

#define pascal      __stdcall

#define cdecl       _cdecl

#ifndef cdecl

#define cdecl       _cdecl

#endif

而c和c++程式的預設呼叫方式則為__cdecl,下圖為vc++6.0的預設設定,因此在不顯式寫明呼叫約定的情況下,一般都是採用__cdecl方式,而在與windows api打交道的場景下,通常都是顯式的寫明使用__stdcall,才能與windows api保持一致。

另外,還要注意的是,如printf此類支援可變引數的函式,由於不知道呼叫者會傳遞多少個引數,也不知道會壓多少個引數入棧,因此函式本身內部不可能清理堆疊,只能由呼叫者清理了。

(2)函式返回值傳遞

出自《程式設計師的自我修養-鏈結、裝載與庫》p299

eax是函式傳遞返回值的乙個通道。

1.對於小於4個位元組的資料函式將返回值儲存在eax中。

2.5~8個位元組物件的情況呼叫慣例都是採用eax和edx的聯合返回方式進行。

3.大於8個位元組的返回型別,用一下**測試:

1 typedef struct

big_thing

2big_thing;56

big_thing return_test()712

13int

main()

14

如果返回值的型別的尺寸太大,c語言在函式的返回時會使用乙個臨時的棧上記憶體作為中轉,結果返回值物件會被拷貝兩次。因而不到萬不得已,不要輕易返回大尺寸物件。

再來看看函式返回乙個c++物件會如何:

1 #include 2

using

namespace

std;34

struct

cpp_obj510

11 cpp_obj(const cpp_obj&c)

1215

16 cpp_obj& opearator=(const cpp_obj&rhs)

1721

22 ~cpp_obj()

2326

};27

28cpp_obj return_test()

2934

intmain()

35

執行後的輸出結果可以得出:函式返回之後,進行了乙個拷貝函式的呼叫,以及一次operator=的呼叫,也就是說,仍然產生了兩次拷貝。因此c++的物件同樣會產生臨時物件。

在這段**中我們還能看到在c++返回乙個物件時,物件要經過兩次拷貝建構函式的呼叫才能夠完成返回物件的傳遞,1次拷貝到棧上的臨時物件裡,另一次把臨時物件拷貝到儲存返回值的物件裡。在某些編譯器裡,返回乙個物件甚至要經過更多的步驟。

為了減少返回物件的開銷,c++提出了返回值優化(rvo)技術,可以將某些場合下的物件拷貝減少一次,例如:

1

cpp_obj return_test()

2

目的是直接將物件的構造在傳出時使用的臨時物件上,減少一次複製過程。

C 函式引數傳遞與返回值優化技巧

很久沒登陸csdn,最後一次發帖是換工作的散分貼,之後背井離鄉,一去就是八年。八年前的自己對技術充滿熱情,但是有些井底之蛙,也有些偏激。八年過去,恍然大悟,技術無論大小,總應該有些積累,有些沉澱,有些能讓自己和後來之人收益之處,於是乎開始著手寫技術文章,文章或許淺顯,但或多或少可以與君交流提高。廢話...

引數傳遞以及返回值

在呼叫乙個方法時,我們經常傳入我們需要的引數,對於基本型別的傳入,在執行方法時直接用即可,這裡僅介紹幾種引用型別的引數傳遞 類名作為形式引數 如果乙個方法的形參要乙個類 型別,就傳入乙個該類的物件 根據 可知,建立物件時完成初始化,此時物件裡的的num時2,在呼叫方法時,傳入30,替代了原來的2,所...

hjr C 函式呼叫與引數傳遞與返回值

c語言就是由很多子函式組成的模組化語言 引數是體現乙個函式靈活性的重要工具 首先說下pc程式指標,pc是乙個暫存器,裡面的值指向當前程式 執行點的位址 既然是指標那麼,可以看這裡了解一下指標hjr教程 c 二 關於指標 指標變數值就是位址,我們的程式是從上往下執行的,程式又是存放到程式空間的,所以每...