(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如果返回值的型別的尺寸太大,c語言在函式的返回時會使用乙個臨時的棧上記憶體作為中轉,結果返回值物件會被拷貝兩次。因而不到萬不得已,不要輕易返回大尺寸物件。big_thing
2big_thing;56
big_thing return_test()712
13int
main()
14
再來看看函式返回乙個c++物件會如何:
1 #include 2執行後的輸出結果可以得出:函式返回之後,進行了乙個拷貝函式的呼叫,以及一次operator=的呼叫,也就是說,仍然產生了兩次拷貝。因此c++的物件同樣會產生臨時物件。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
在這段**中我們還能看到在c++返回乙個物件時,物件要經過兩次拷貝建構函式的呼叫才能夠完成返回物件的傳遞,1次拷貝到棧上的臨時物件裡,另一次把臨時物件拷貝到儲存返回值的物件裡。在某些編譯器裡,返回乙個物件甚至要經過更多的步驟。
為了減少返回物件的開銷,c++提出了返回值優化(rvo)技術,可以將某些場合下的物件拷貝減少一次,例如:
1目的是直接將物件的構造在傳出時使用的臨時物件上,減少一次複製過程。cpp_obj return_test()
2
C 函式引數傳遞與返回值優化技巧
很久沒登陸csdn,最後一次發帖是換工作的散分貼,之後背井離鄉,一去就是八年。八年前的自己對技術充滿熱情,但是有些井底之蛙,也有些偏激。八年過去,恍然大悟,技術無論大小,總應該有些積累,有些沉澱,有些能讓自己和後來之人收益之處,於是乎開始著手寫技術文章,文章或許淺顯,但或多或少可以與君交流提高。廢話...
引數傳遞以及返回值
在呼叫乙個方法時,我們經常傳入我們需要的引數,對於基本型別的傳入,在執行方法時直接用即可,這裡僅介紹幾種引用型別的引數傳遞 類名作為形式引數 如果乙個方法的形參要乙個類 型別,就傳入乙個該類的物件 根據 可知,建立物件時完成初始化,此時物件裡的的num時2,在呼叫方法時,傳入30,替代了原來的2,所...
hjr C 函式呼叫與引數傳遞與返回值
c語言就是由很多子函式組成的模組化語言 引數是體現乙個函式靈活性的重要工具 首先說下pc程式指標,pc是乙個暫存器,裡面的值指向當前程式 執行點的位址 既然是指標那麼,可以看這裡了解一下指標hjr教程 c 二 關於指標 指標變數值就是位址,我們的程式是從上往下執行的,程式又是存放到程式空間的,所以每...