c/c++参数入栈顺序和参数计算顺序
ID:技术让梦想更伟大
作者:李肖遥
如果大家细心的话应该知道c/c++语言函数参数入栈顺序为从右至左,那么为什么这样呢?来看看两个知识点:参数的计算顺序与压栈顺序。
参数入栈顺序
c/c++中规定了函数参数的压栈顺序是从右至左,函数调用协议会影响函数参数的入栈方式、栈内数据的清除方式、编译器函数名的修饰规则等。
参数传递和命名约定
Visual C/C++ 编译器支持以下调用约定。
关键字 | 堆栈清理 | 参数传递 |
---|---|---|
__cdecl | Caller | 以相反的顺序(从右到左)将参数压入堆栈 |
__clrcall | n/a | 按顺序(从左到右)将参数加载到 CLR 表达式堆栈 |
__stdcall | Callee | 以相反的顺序(从右到左)将参数压入堆栈 |
__fastcall | Callee | 存储在寄存器中,然后压入堆栈 |
__thiscall | Callee | 压入堆栈;此指针存储在 ECX 中 |
__vectorcall | Callee | 存储在寄存器中,然后以相反的顺序(从右到左)压入堆栈 |
官方详解可见:
https://msdn.microsoft.com/en-us/library/984x0h58(v=vs.120).aspx
通常情况下c/c++默认入栈方式:__cdel
,也就是以右到左将参数压入堆栈,Windows api使用的是__stdcall
方式,__fastcall
适用于对性能要求较高的场合。
自定义参数入栈形式
当然我们也可以自定义函数的入栈顺序,常用形式如下
//函数返回值 入栈规则 函数名(参数类型 参数名);
int __cdecl get_name_index(const std::string& str_name);
为什么要从右往左入栈?
每个参数都有自己的地址,但不定长参数无法确认地址,并且函数参数的个数也不确定,C/C++中规定了函数参数的压栈顺序是从右至左,对于含有不定参数的printf函数,其原型是printf(const char* format,…);
其中format确定了printf的参数(通过format的%个数判断)。
假设是从左至右压栈,那么先入栈的是format,然后依次入栈未知参数,此时想要知道参数个数,就必须找到format,而要找到format,就必须知道参数个数,这样就会陷入一个死胡同里面了。
而c/c++中规定参数压栈为从右至左的顺序,这种方式对于不定参数,最后入栈的是参数个数,只需要取栈顶就可以得到。
我们举一个了例子如下:
//win10+vs2019
//来源:技术让梦想更伟大
//作者:李肖遥
#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;
}
输出结果如下:
我们知道先入栈的占高地址,从结果看出入栈的顺序依次为z->y->x
,即压栈顺序从右至左。
参数计算顺序
先执行哪个参数和参数的计算顺序有关,而c/c++中没有规定函数参数的计算顺序,这个和编译器有关,代码参数的计算顺序决定了实际输出。
//来源:技术让梦想更伟大
//作者:李肖遥
#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输出结果 2 4 7
vs的计算顺序是从右至左,clang的计算顺序是从左至右,具体的计算流程分析就很简单了。
对于c/c++函数参数的读取顺序,参数入栈时顺序从右向左入栈,但是在入栈前会先把参数列表里的表达式从右向左算一遍得到表达式的结果,最后再把这些运算结果统一入栈。
在参数入栈前,编译器会先把参数的表达式都处理掉,对于一般的操作来说,参数入栈时取值是直接从变量的内存地址里取的,但是对于a++操作,编译器会开辟一个缓冲区来保存当前a的值,然后再对a继续操作,最后参数入栈时的取值是从缓冲区取,而不是直接从a的内存地址里取。
结论
因为函数参数的计算顺序依照编译器的实现,所以在编码中避免编写诸如 fun(++x, x+y)
这种的程序,其在不同的平台得到的结果可能不一样,但是在面试中可能遇到这样的问题,所以我们需要知其然更要知所以然。
嵌入式编程专辑 Linux 学习专辑 C/C++编程专辑 Qt进阶学习专辑
关注我的微信公众号,回复“加群”按规则加入技术交流群。
点击“阅读原文”查看更多分享。