c/c++參數(shù)入棧順序和參數(shù)計(jì)算順序
ID:技術(shù)讓夢(mèng)想更偉大
作者:李肖遙
如果大家細(xì)心的話應(yīng)該知道c/c++語(yǔ)言函數(shù)參數(shù)入棧順序?yàn)閺挠抑磷?,那么為什么這樣呢?來(lái)看看兩個(gè)知識(shí)點(diǎn):參數(shù)的計(jì)算順序與壓棧順序。
參數(shù)入棧順序
c/c++中規(guī)定了函數(shù)參數(shù)的壓棧順序是從右至左,函數(shù)調(diào)用協(xié)議會(huì)影響函數(shù)參數(shù)的入棧方式、棧內(nèi)數(shù)據(jù)的清除方式、編譯器函數(shù)名的修飾規(guī)則等。
參數(shù)傳遞和命名約定
Visual C/C++ 編譯器支持以下調(diào)用約定。
| 關(guān)鍵字 | 堆棧清理 | 參數(shù)傳遞 |
|---|---|---|
| __cdecl | Caller | 以相反的順序(從右到左)將參數(shù)壓入堆棧 |
| __clrcall | n/a | 按順序(從左到右)將參數(shù)加載到 CLR 表達(dá)式堆棧 |
| __stdcall | Callee | 以相反的順序(從右到左)將參數(shù)壓入堆棧 |
| __fastcall | Callee | 存儲(chǔ)在寄存器中,然后壓入堆棧 |
| __thiscall | Callee | 壓入堆棧;此指針存儲(chǔ)在 ECX 中 |
| __vectorcall | Callee | 存儲(chǔ)在寄存器中,然后以相反的順序(從右到左)壓入堆棧 |
官方詳解可見(jiàn):
https://msdn.microsoft.com/en-us/library/984x0h58(v=vs.120).aspx
通常情況下c/c++默認(rèn)入棧方式:__cdel,也就是以右到左將參數(shù)壓入堆棧,Windows api使用的是__stdcall方式,__fastcall適用于對(duì)性能要求較高的場(chǎng)合。
自定義參數(shù)入棧形式
當(dāng)然我們也可以自定義函數(shù)的入棧順序,常用形式如下
//函數(shù)返回值 入棧規(guī)則 函數(shù)名(參數(shù)類(lèi)型 參數(shù)名);
int __cdecl get_name_index(const std::string& str_name);
為什么要從右往左入棧?
每個(gè)參數(shù)都有自己的地址,但不定長(zhǎng)參數(shù)無(wú)法確認(rèn)地址,并且函數(shù)參數(shù)的個(gè)數(shù)也不確定,C/C++中規(guī)定了函數(shù)參數(shù)的壓棧順序是從右至左,對(duì)于含有不定參數(shù)的printf函數(shù),其原型是printf(const char* format,…);其中format確定了printf的參數(shù)(通過(guò)format的%個(gè)數(shù)判斷)。
假設(shè)是從左至右壓棧,那么先入棧的是format,然后依次入棧未知參數(shù),此時(shí)想要知道參數(shù)個(gè)數(shù),就必須找到format,而要找到format,就必須知道參數(shù)個(gè)數(shù),這樣就會(huì)陷入一個(gè)死胡同里面了。
而c/c++中規(guī)定參數(shù)壓棧為從右至左的順序,這種方式對(duì)于不定參數(shù),最后入棧的是參數(shù)個(gè)數(shù),只需要取棧頂就可以得到。
我們舉一個(gè)了例子如下:
//win10+vs2019
//來(lái)源:技術(shù)讓夢(mèng)想更偉大
//作者:李肖遙
#include <iostream>
using namespace std;
void fun(int x, int y, int z)
{
cout << x << &x << endl;
cout << y << &y << endl;
cout << z << &z << endl;
}
int main(int argc, char *argv[])
{
fun(1, 2, 3);
system("pause");
return 0;
}
輸出結(jié)果如下:

我們知道先入棧的占高地址,從結(jié)果看出入棧的順序依次為z->y->x,即壓棧順序從右至左。
參數(shù)計(jì)算順序
先執(zhí)行哪個(gè)參數(shù)和參數(shù)的計(jì)算順序有關(guān),而c/c++中沒(méi)有規(guī)定函數(shù)參數(shù)的計(jì)算順序,這個(gè)和編譯器有關(guān),代碼參數(shù)的計(jì)算順序決定了實(shí)際輸出。
//來(lái)源:技術(shù)讓夢(mèng)想更偉大
//作者:李肖遙
#include <stdio.h>
int main () {
int a = 2;
printf("%d, %d, %d", a, (a = (a + 2)), (a = (a + 3)));
system("pause");
return 0;
}
//win10 + VS2019 輸出: 7, 7, 7
//clang輸出結(jié)果 2 4 7
vs的計(jì)算順序是從右至左,clang的計(jì)算順序是從左至右,具體的計(jì)算流程分析就很簡(jiǎn)單了。
對(duì)于c/c++函數(shù)參數(shù)的讀取順序,參數(shù)入棧時(shí)順序從右向左入棧,但是在入棧前會(huì)先把參數(shù)列表里的表達(dá)式從右向左算一遍得到表達(dá)式的結(jié)果,最后再把這些運(yùn)算結(jié)果統(tǒng)一入棧。
在參數(shù)入棧前,編譯器會(huì)先把參數(shù)的表達(dá)式都處理掉,對(duì)于一般的操作來(lái)說(shuō),參數(shù)入棧時(shí)取值是直接從變量的內(nèi)存地址里取的,但是對(duì)于a++操作,編譯器會(huì)開(kāi)辟一個(gè)緩沖區(qū)來(lái)保存當(dāng)前a的值,然后再對(duì)a繼續(xù)操作,最后參數(shù)入棧時(shí)的取值是從緩沖區(qū)取,而不是直接從a的內(nèi)存地址里取。
結(jié)論
因?yàn)楹瘮?shù)參數(shù)的計(jì)算順序依照編譯器的實(shí)現(xiàn),所以在編碼中避免編寫(xiě)諸如 fun(++x, x+y)這種的程序,其在不同的平臺(tái)得到的結(jié)果可能不一樣,但是在面試中可能遇到這樣的問(wèn)題,所以我們需要知其然更要知所以然。
嵌入式編程專(zhuān)輯 Linux 學(xué)習(xí)專(zhuān)輯 C/C++編程專(zhuān)輯 Qt進(jìn)階學(xué)習(xí)專(zhuān)輯
關(guān)注我的微信公眾號(hào),回復(fù)“加群”按規(guī)則加入技術(shù)交流群。
點(diǎn)擊“閱讀原文”查看更多分享。
