可变长参数列表

该段代码输出()

1
2
3
4
5
6
7
int main()
{
int arr[]={1,2,3,4,5,6,7};
int *p=arr;
printf("%d,%d\n",*p,*(++p));
return 0;
}

A. 3,3 B. 2,2 C. 2,3 D. 3,2

参数入栈顺序:

通常情况下c/c++默认入栈方式为__cdecl

关键字 堆栈清理 参数传递
__cdecl Caller 以相反的顺序(从右到左)将参数压入堆栈
__clrcall n/a 按顺序(从左到右)将参数加载到 CLR 表达式堆栈
__stdcall Callee 以相反的顺序(从右到左)将参数压入堆栈
__fastcall Callee 存储在寄存器中,然后压入堆栈
__thiscall Callee 压入堆栈;此指针存储在 ECX 中
__vectorcall Callee 存储在寄存器中,然后以相反的顺序(从右到左)压入堆栈

为什么要从右往左入栈? 因为要支持可变长参数,如果从左向右,编译器就不知道用户传入了多少实参。  参数的信息是由第一个参数fmt...确定的(如printf(“%s %s”,str1 ,str2)的参数信息是通过检测两个%s来获取的)。若从左向右压栈,fmt...就被放入了栈底,而栈顶指针由于不清楚各个参数信息,就无法确定偏移量来指向fmt...,所以也无法指向各个参数。相反,若从右向左压栈,fmt...就存放在栈顶,可以直接通过它读取各参数的信息从而确定偏移量。

所以上题,是先处理*(++p),再处理*p,故而选A。

类型检查与默认参数提升:

  • 可变参数部分不会执行类型检查,因为其原型不能提供可变参数的数目和类型。同时,调用可变参数函数之前必须有其函数原型(这像废话,但在C语言中,函数声明不是必须的,引入头文件(其中的原型)主要作用是用于参数提示和类型检查,而C++中,调用之前必须有原型,否则会报错。

  • 可变参数会进行默认参数提升: charshort总是被提升为intfloat总是被提升为double,所以%f可以同时表示floatdouble