本篇文章于 823 天前发表,某些内容可能已经过时,请注意甄别。

可变长参数列表

该段代码输出()

c++
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