c++參數入棧順序和參數計算順序


關於

本文涉及到代碼,演示環境為:win10 + VS2017 ,ubuntu+clang
clang版本:

參數入棧順序

順序

幾種常見的函數參數入棧順序,還有兩種就不介紹了(__clrcall、__thiscall)

順序 釋義
__cdecl 函數參數按照從右到左的順序入棧,並且由調用函數者把參數彈出棧以清理堆棧
__stdcall 函數參數按照從右到左的順序入棧,被調用的函數在返回前清理傳送參數的棧,函數參數個數固定
__fastcall 使用內部寄存器ECX,EDX傳遞前兩個DWORD 或者size更小的參數

__fastcall用的很少。 通常情況下: c/c++默認入棧方式:__cdel。Windows api使用的是__stdcall方式。

自定義參數入棧形式

可以自定義函數的入棧順序。 常用形式如下

函數返回值  入棧規則  函數名(參數類型 參數名);

一個例子

int __cdecl get_name_index(const std::string& str_name);

為什么要從右往左入棧?

  • 結論:為了確定函數參數個數。大膽猜想:統一定長參數函數和不定長參數函數處理函數入棧順序。
  • 有的函數參數個數是確定的,比如上面的函數,就一個參數,有的函數參數是不定長,學過C的都知道printf和scanf,當然也可以自定義變長參數,c++11引入了可變長參數。
  • 定長參數的函數,當然,每個參數都有自己的地址,很容易就拿到了,but, 不定長參數呢,怎么知道每個參數的地址和壓入多少個參數? 比如printf和scanf函數,第一個參數就很特別,一個格式化的字符串。printf是怎么知道有多少個參數呢? 通過格式格式字符串個數確定。有多少個格式字符串,就需要多少個參數

注意: 棧是先進后出,俗稱后來居上。

  • 顯然,定長函數參數就不需要分析了,每個參數都有自己的地址,能確定下來,從左往右 對比 從右往左沒有區別。
  • 那不定長函數參數呢? 以 printf函數為例,
int a = 1;
int b = 2;
printf("%d, %d", a, b);

情況A: 從左往右。 那么上面的代碼,先入棧的是"%d, %d", 再是a, 最后是b。printf需要知道壓入了多少個參數,就需要檢查格式化字符串"%d, %d", 但是,這個參數被壓入了棧底。棧頂是b。肉眼當然知道壓入了多少個參數,編譯器需要通過判斷條件才能知道,判斷條件被壓入棧底,這時,編譯器就無法知道格式化字符串了,也就不知道參數個數了。

情況B從右往左。 那么上面的代碼,先入棧的是b,再是a,最后是"%d, %d". printf先檢查第一個參數,棧頂是"%d, %d", 就可以知道壓入了多少個參數了。

參數計算順序

這個和編譯器有關。不同編譯器可能也相同。

一定要知道

Note: 期望輸出參數計算順序 息息相關。
為什么? 代碼參數的計算順序決定了實際輸出,這與期望輸出是兩碼事。 而期望輸出是主觀預測輸出結果。

我將使用下面的測試代碼,觀察VS和clang兩種編譯器的參數計算順序。

int a = 2;
printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3)));

win10 + VS2017

使用VS2017輸出下面的代碼,可知計算順序。可自己先計算下輸出結果是什么....

正確輸出: 7, 7, 7

分析(個人理解): printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3))); 可以拆解為下面的代碼:

// printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3)));
a = a + 3;
a = a + 2
a 

printf函數先將表達式的結果計算出來,得到結算結果,再將計算結果壓入棧。先計算a=a+3;, 此時, a的值從2->5, 同理,a=a+2;后,a的值從5->7。當執行函數printf時,變成了

printf("%d, %d, %d", 7, 7, 7);
  • 按照上面的分析方法,再來一個例子
int a = 2;
int b = 3;

printf("%d, %d, %d", a, a = (a+b), b = (b-a));

輸出結果

printf("%d, %d, %d", a, a = (a+b), b = (b-a));
// 等效下面的方式  
1. b = b - a;// b = 3-2; b = 1
2. a = a +b; // a = 2 + 1; a = 3
3. printf("%d, %d, %d", 3, 3, 1); 

ubuntu + clang

自己先計算結果,再看答案。
clang輸出結果

可見,clang的計算順序是從左至右,而VS的計算順序是從右至左
為什么clang的輸出結果是這樣的? 大膽猜想(個人意見)先計算a, 將a單獨一個副本,再計算 a=a+2, 結果為4,再將a=a+2的結果4保存到另一個副本中,最后計算a=a+3=4+3 = 7,將(a=a+3)拷貝到下一個副本中。

結論

應該避免上述情況,即可實現同樣的表達式,可以實現在不同的平台得到同樣的結果。

踩坑

這點倒是深有體會,自己寫公共服務模塊時,就因為上面的情況出現了一些困擾。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM