变元函数和main()
#include <stdio.h>
#include <stdarg.h>
int f(long x,...)
{ va_list ap;
int i=0;
va_start(ap,x);
while(x)
{ i++;
printf("%ld ", x);
x=va_arg(ap,long);
}
va_end(ap);
printf("\n");
return i;
}
int main()
{ return f(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,1L<<63,0);
}
在gcc、linux和x64上:即使f()的参数没有被转换为64位长,gcc似乎也做对了。
$ gcc t.c && ./a.out
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 -9223372036854775808
怎么做?
3条答案
按热度按时间djp7away1#
在linux x64上,变量函数的参数被“提升”为64位值,因此在此平台上不需要显式转换为64位值。
ipakzgxi2#
这里发生了三件事:
1.缺省参数提升
C标准为变量参数定义了一个名为“default argument promotion“的东西。
float
,则在函数调用之前将其提升为double
类型。signed char
或unsigned char
、signed short
或unsigned short
、枚举类型或位字段都使用整数提升转换为有符号整型或无符号整型。但是要注意:这是
int
,在x64 linux上是32位的。2. System V x64调用约定
SysV AMD64 ABI定义了如何将参数传递给3.2.3节中的函数。这里重要的是:前6个整数在寄存器中传递,其余的被压入堆栈。“每个参数的大小都被四舍五入到八个字节。[...]因此堆栈将始终是八个字节对齐的”。注意,浮点数是在浮点寄存器中传递的,而不是普通的。
3. va_...宏
包含
va_start
的函数会得到一个特殊的序言,它也会将所有的参数寄存器压入堆栈。如何完成并不是调用约定的一部分。由于编译器此时并不知道实际的参数大小,所以可以预期它会压入所有的寄存器,使这些值也是64位对齐的。va_arg
然后首先遍历这些寄存器。然后是传递到堆栈上的其余参数。这对您的代码示例意味着什么
所有这一切意味着所有参数都是64位对齐的。但它们实际上并不是64位宽。所有整数至少是32位宽(
_Bool
/bool
是8位宽),仅此而已。未使用位的值是未指定的。调用者可以自由地将这些位保留为未初始化。因此:vdzxcuhz3#
使其工作的基本代码是
你可以把它换成任何其他类型,很好地击中自己的脚。
根据目标体系结构的规则,每次访问后
ap
可能会增加1、2、4或8。