C函式引數傳遞與返回值傳遞
(1)引數傳遞
__stdcall和__cdecl都是函式呼叫約定關鍵字,先給出這兩者的區別,然後舉例項分析:
__stdcall:引數由右向左壓入堆疊;堆疊由函式本身清理。
__cdecl:引數也是由右向左壓入堆疊;但堆疊由呼叫者清理。
下面給出例項分析:
- #include "stdio.h"
- #include <iostream>
- #include <Windows.h>
- #include <conio.h>
- usingnamespace std;
-
int __stdcall Func_stdcall(
- {
- return 1;
- }
- int __cdecl Func_cdecl(int nParam1, int nParam2)
- {
- return 1;
- }
- int main()
- {
- int a = Func_stdcall(1, 2);
- a = Func_cdecl(1, 2);
- return 0;
- }
以上程式碼在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中,可見如下程式碼:
- #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此類支援可變引數的函式,由於不知道呼叫者會傳遞多少個引數,也不知道會壓多少個引數入棧,因此函式本身內部不可能清理堆疊,只能由呼叫者清理了。
出自《程式設計師的自我修養-連結、裝載與庫》P299
eax是函式傳遞返回值的一個通道。
1.對於小於4個位元組的資料函式將返回值儲存在eax中。
2.5~8個位元組物件的情況呼叫慣例都是採用eax和edx的聯合返回方式進行。
3.大於8個位元組的返回型別,用一下程式碼測試:
1 typedef struct big_thing 2 { 3 char buf[128]; 4 }big_thing; 5 6 big_thing return_test() 7 { 8 big_thing b; 9 b.buf[] = 0; 10 return b; 11 } 12 13 int main() 14 { 15 big_thing n = return_test(); 16 }
- 首先main函式在棧額外開闢了一片空間,並將這塊空間的一部分作為傳遞返回值的臨時物件,這裡稱為temp
- 將temp物件的地址作為隱藏引數傳遞個return_test函式
- return_test 函式將資料拷貝給temp物件,並將temp物件的地址用eax傳出。
- return_test返回以後,mian函式將eax 指向的temp物件的內容拷貝給n。
如果返回值的型別的尺寸太大,c語言在函式的返回時會使用一個臨時的棧上記憶體作為中轉,結果返回值物件會被拷貝兩次。因而不到萬不得已,不要輕易返回大尺寸物件。
再來看看函式返回一個C++物件會如何:
1 #include <iostream> 2 using namespace std; 3 4 struct cpp_obj 5 { 6 cpp_obj() 7 { 8 cout << "ctor\n"; 9 } 10 11 cpp_obj(const cpp_obj& c) 12 { 13 cout << "copy ctor\n"; 14 } 15 16 cpp_obj& opearator=(const cpp_obj& rhs) 17 { 18 cout << "operator = \n"; 19 return *this; 20 } 21 22 ~cpp_obj() 23 { 24 cout << "dtor\n"; 25 } 26 }; 27 28 cpp_obj return_test() 29 { 30 cpp_obj b; 31 cout << "before return\n"; 32 return b; 33 } 34 int main() 35 { 36 cpp_obj n; 37 n = return_test(); 38 }
執行後的輸出結果可以得出:函式返回之後,進行了一個拷貝函式的呼叫,以及一次operator=的呼叫,也就是說,仍然產生了兩次拷貝。因此C++的物件同樣會產生臨時物件。
在這段程式碼中我們還能看到在c++返回一個物件時,物件要經過兩次拷貝建構函式的呼叫才能夠完成返回物件的傳遞,1次拷貝到棧上的臨時物件裡,另一次把臨時物件拷貝到儲存返回值的物件裡。在某些編譯器裡,返回一個物件甚至要經過更多的步驟。
為了減少返回物件的開銷,C++提出了返回值優化(RVO)技術,可以將某些場合下的物件拷貝減少一次,例如:
1 cpp_obj return_test() 2 { 3 return cpp_obj(); 4 }
目的是直接將物件的構造在傳出時使用的臨時物件上,減少一次複製過程。
相關推薦
C函式引數傳遞與返回值傳遞
(1)引數傳遞 __stdcall和__cdecl都是函式呼叫約定關鍵字,先給出這兩者的區別,然後舉例項分析: __stdcall:引數由右向左壓入堆疊;堆疊由函式本身清理。 __cdecl:引數也是由右向左壓入堆疊;但堆疊由呼叫者清理。
Javascript函式引數都是按值傳遞
網站資訊 文章數:581 篇 評論數:2006 條 標籤數:1184 個 頁面數:7 個 友鏈數:20 條 使用者數:13092 位 共執行:2562 天 建站日期:2011.11.17 最近更新:2018.11.17 註冊登入 據說本站已備案,不管你信不信,反正我信了. ^_^
javascript中所有函式引數都是按值傳遞
在看《JavaScript高階程式設計》(第三版)的時候,傳遞引數這一節,裡面提到 ECMAScript中所有函式的引數都是按值傳遞的 它自己的解釋是, 把函式外部的值複製給函式內部的引數,就和把值從一個變數複製到另一個變數一樣。 基本型別值的傳遞如同基本型別變數的複製一樣, 而引用型別值的傳遞,則如同
C函式與彙編函式之間引數及返回值傳遞方法
AAPCS對ARM結構的一些標準做了定義,在這裡我們只重點介紹函式呼叫部分,如圖8所示,AAPCS為ARM的R0~R15暫存器做了定義,明確了它們在函式中的職責: 圖 8 AAPCS關於ARM暫存器的定義 一、函式呼叫時的規則如下: 1、 父函式與子
C語言內嵌彙編程式設計--函式引數傳遞,返回值
本文內容較為基礎,適合彙編新手(慚愧,本人就是)學習參考。內嵌程式設計:函式體用匯編實現,儲存在.asm檔案中;在.asm和.c檔案中宣告;在.c檔案中呼叫,呼叫方式和普通函式相同。函式引數傳遞 剛開始想用偷懶,預設傳入引數較少的函式,引數直接傳入cx、dx,從通用暫存
C++函式返回值傳遞
C++函式返回可以按值返回和按常量引用返回,偶爾也可以按引址返回。多數情況下不要使用引址返回。 使用按值返回總是很安全的,但是如果返回物件為類型別的,則更好的方法是按常量引用返回以節省複製開銷。必須確保返回語句中的表示式在函式返回時依然有效。 const string& findMax(co
C# 函式引數傳遞(按值和引用)
先來說下C#中的資料型別.分值型別和引用型別兩大類. 值型別:直接儲存資料的值,儲存在記憶體中的stack(堆疊)中 引用型別:儲存對值的引用,實際上儲存的就是一個記憶體的地址.引用型別的儲存分成兩塊,實際值儲存在託管堆(heap)中.實際值的記憶體地址儲存在
關於C++裡面的函式中,按值傳遞與按引用傳遞的區別
在c++中,一般有兩種傳遞方式:一種是引用按值傳遞,另一種是按引用傳值, 其我們經常在java中寫一些方法呼叫,當傳遞基本型別時,都是按指傳遞,在 傳遞物件時,都是按引用型別傳遞。 那麼這兩種
關於 Shell 引數傳遞 與 預設值
簡介 除了基本的獲取指令碼執行時的傳入引數外, 還有更便捷的語法糖: 引數預設值, 自動賦值. 基本傳參 先來一個示例: #!/bin/sh echo 引數0: $0; echo 引數1: $1; echo 引數2: $2; echo 引數3: $3; echo 引數4: $4; 執行測試
JAVA引數傳遞方式 (按值傳遞與引用傳遞區別)
首先要明確的是JAVA中沒有引用傳遞, 全部是按值呼叫 令大家所費解的 當物件引用作為引數時 函式為什麼能修改真實的物件呢?這不是引用傳遞的特徵嗎? 尤其先學習C++再學習JAVA的同學(比如說我自己)會這樣認為, 用白話解釋就是: 引用傳遞指標時, 連函式操作的指
C++函式引數傳遞的3種方式以及優缺點(轉)
寫函式時遇到給予函式的引數變數無法被修改的問題,轉自:https://blog.csdn.net/zhaoxun91/article/details/75417492 1 函式引數傳遞的3種方式比較 1.1 按值傳遞 #include <iostream> using names
c++ 函式引數傳遞
指標形參 當函式使用指標作為形參時, 本質也是使用傳值引數, 只是傳遞的是變數的地址,所以可以通過指標修改它所指的物件的值, 但是在c++ 中推薦使用引用型別的形參替代指標。 傳引用引數 使用引用傳參可以避免拷貝操作, 提搞程式的效率。 尤其是
lua函式中的引數與返回值與print函式
function hanshu1() a=2222 print("111111111111") print(a) end function hanshu2(a,b,c,d) print(a,
Android 自定義PopupWindow以及引數傳遞與返回
在這篇部落格之前,還寫了一篇關於PopupWindow,那篇主要是關於PopupWindow彈出位置的設定。以及選擇PopupWindow佈局後的監聽。詳情看Android popupwindow 示例程式一。接下來這篇主要是講自定義PopupWindow以及引數傳遞與返
C++函數返回值傳遞
使用 spa 表達 turn index 有效 urn 但是 string C++函數返回可以按值返回和按常量引用返回,偶爾也可以按引址返回。多數情況下不要使用引址返回。 使用按值返回總是很安全的,但是如果返回對象為類類型的,則更好的方法是按常量引用返回以節省復制開銷。必須
C++三種引數傳遞方法(值傳遞、指標傳遞、引用傳遞)的一些知識
C++的函式引數傳遞有三種方法:值傳遞、引用傳遞、指標傳遞 。其中引用傳遞和指標傳遞幾乎一樣,只不過引用傳遞在使用時比指標更安全。 (1)關於函式返回一個物件 當你的函式返回型別是【非引用】的型別時,return時先呼叫該類的拷
C++函式引數和返回值
形式引數和實際引數 在呼叫函式時,大多數情況下,函式是帶引數的。主調函式和被呼叫函式之間有資料傳遞關係。前面已提到:在定義函式時函式名後面括號中的變數名稱為形式引數(formal parameter,簡稱形參),在主調函式中呼叫一個函式時,函式名後面括號中的引數(可以是一個表示式)稱為實際引數(actual
【轉】C++函式引數傳遞中的一級指標和二級指標【【**】】
主要內容: 1、一級指標和二級指標 2、函式指標傳遞的例子 3、什麼時候需要傳遞二級指標? 4、二級指標在連結串列中的使用 1、一級指標和二級指標 一級指標:即我們一般說的指標,就是記憶體地址; 二級指標:指向指標的指標,就是
C語言程式設計基礎-09函式與返回值及形參
函式 返回值 形參實參 函式 在大規模的程式中需要對語句進行分組管理,把相互之間聯絡比較緊密的語句合併成一組; 分組可以在多個不同層次上進行,最低一級分組的結果叫程式碼塊,程式碼塊由{}大括號包括; 在大括號前面新增 型別名 函式名()的就是函式; 函式的形式如 v
函式的引數與返回值
如果把函式比喻成一臺機器,那麼引數就是原材料,返回值就是最終產品;函式的作用就是根據不同的引數產生不同的返回值。函式的引數在函式定義中出現的引數可以看做是一個佔位符,它沒有資料,只能等到函式被呼叫時接收傳遞進來的資料,所以稱為形式引數,簡稱形參。函式被呼叫時給出的引數包含了實