1. 程式人生 > >C函式引數傳遞與返回值傳遞

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

(1)引數傳遞

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

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

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

  下面給出例項分析:

  1. #include "stdio.h"
  2. #include <iostream>
  3. #include <Windows.h>
  4. #include <conio.h>
  5. usingnamespace std;  
  6. int __stdcall Func_stdcall(
    int nParam1, int nParam2)  
  7. {  
  8.     return 1;  
  9. }  
  10. int __cdecl Func_cdecl(int nParam1, int nParam2)  
  11. {  
  12.     return 1;  
  13. }  
  14. int main()    
  15. {  
  16.     int a = Func_stdcall(1, 2);  
  17.     a = Func_cdecl(1, 2);  
  18.     return 0;    
  19. }   

  以上程式碼在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中,可見如下程式碼:

  1. #define CALLBACK    __stdcall
  2. #define WINAPI      __stdcall
  3. #define WINAPIV     __cdecl
  4. #define APIENTRY    WINAPI
  5. #define APIPRIVATE  __stdcall
  6. #define PASCAL      __stdcall
  7. #define cdecl       _cdecl
  8. #ifndef CDECL
  9. #define CDECL       _cdecl
  10. #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

函式引數返回

如果把函式比喻成一臺機器,那麼引數就是原材料,返回值就是最終產品;函式的作用就是根據不同的引數產生不同的返回值。函式的引數在函式定義中出現的引數可以看做是一個佔位符,它沒有資料,只能等到函式被呼叫時接收傳遞進來的資料,所以稱為形式引數,簡稱形參。函式被呼叫時給出的引數包含了實