可变参数函数和 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
如何?
最佳答案
这里发生了三件事:
1。默认参数提升
C 标准定义了“default argument promotion” ” 对于可变参数。那means :
- 如果实际参数的类型为
float
,则在函数调用之前将其提升为double
类型。 - 任何
signed char
或unsigned char
、signedshort
或unsignedshort
、枚举类型或位字段使用积分提升将其转换为有符号整型或无符号整型。
但要注意:这是 int
,在 x64 Linux 上是 32 位。还有更多事情发生:
2。 System V x64 调用约定
SysV AMD64 ABI定义如何将参数传递给第 3.2.3 节中的函数。这里重要的是:前 6 个整数在寄存器中传递,其余的被压入堆栈。另外:“每个参数的大小四舍五入为八字节。 [...] 因此堆栈将始终是八字节对齐的”。请注意, float 是在浮点寄存器中传递的,而不是在普通寄存器中传递的。
3。 va_... 宏
包含va_start
的函数有一个特殊的序言,它还将所有参数寄存器推送到堆栈。如何完成此操作不属于调用约定的一部分。由于编译器此时不知道实际参数大小,因此您可以期望它推送整个寄存器,使这些值也 64 位对齐。
va_arg
然后首先遍历这些参数,然后遍历堆栈上传递的其余参数。
这对您的代码示例意味着什么
所有这些意味着所有参数都是 64 位对齐的。但它们实际上并不是 64 位宽。所有整数至少为 32 位宽(_Bool
/bool
为 8 位宽),仅此而已。未使用的位的值未指定。调用者可以自由地保留这些位未初始化。因此:
- 代码因负值而损坏 ( godbold )
- 该规范不保证垃圾数据不会在未来某个时刻出现在较高位中。但这似乎不太可能,因为很多代码可能依赖于这种行为(请参阅注释)。
关于c - gcc (x64) 如何处理可变参数函数中的类型/大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49815700/