长期以来大家都有一个疑问,C++函数参数的入栈顺序倒底是怎样的呢?
经验丰富的程序员一定会说C++参数的传输顺序是从右到左的,
这一点大家不妨可以自己建立一个控制台程序,看看函数入参的栈地址,你会发现栈底是最右边的参数,栈顶是最左边的参数,
事实胜于雄辩?
是的,从以上的事实来说经验是正确的.
但是同样也是错误的,这就跟我们说一个不带度量单位的数字一样,任何对的事情都是有限定条件的.
这里大家看到的实际上只是C++编译器默认的配置,实际上是怎样的呢?
实际上默认情况下C++使用__cdecl模式,因此是从右到左的传参顺序.
答案MS都写在了MSDN里,如下:
Argument Passing and Naming Conventions
参数传递和命名约定
如下表所示:
Keyword | Stack cleanup | Parameter passing |
---|---|---|
__cdecl | Caller | Pushes parameters on the stack, in reverse order (right to left) //从右到左,默认工程都使用的这个选项 |
__clrcall | n/a | Load parameters onto CLR expression stack in order (left to right). //这里有个例外 |
__stdcall | Callee | Pushes parameters on the stack, in reverse order (right to left) |
__fastcall | Callee | Stored in registers, then pushed on stack |
__thiscall | Callee | Pushed on stack; this pointer stored in ECX |
Note:
The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.
这里是一个注意事项,在用__stdcall调用时,如果是变长,编译器会自动修改为__cdecl.
但这里还有个问题,为什么采用从右到左的参数方式,而不使用从左到右的传参方式呢?
是的,这个问题很好,其实主要原因就在于变长参数.
一般我们命名一个变长函数时都类似于int display(int i, ...);这种格式,
注意参数都是用压栈方式实现的,
这里我用反证法来举证,
假如使用从左到右的传参方式,栈顶看到的是最后一个参数,那么我怎样知道首参数是哪一个呢?
因此要想知道首参数的值,则必须要知道参数的长度.而栈里并不知道这个长度,那么就无法通过指针偏移的方式找到首参数.
如果使用从右到左的传参方式,栈顶看到的就是左边输入的首参数,因此,无论怎样的变长,都可以通过指针偏移的方式找到值.
这个就是从右到左传参的奥秘! 不知道各位弄明白没有?
附录:
#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>
int display(int i, ...);
int _tmain(int argc, _TCHAR* argv[])
{
display(1,2,3,4,5);
return 0;
}
int display(int i, ...)
{
va_list argptr;
va_start(argptr, i);
int n = i;
while (1)
{
printf( "%d\n", n );
n = va_arg( argptr, int );
if ((n < 0) || (n > 100000))
{
break;
}
}
va_end(argptr);
return 0;
}