[英]How does gcc (x64) deal with types/sizes in variadic functions?
A variadic function and main()可变参数 function 和 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);
}
On gcc, linux and x64: even though f()'s arguments are not cast to a 64bit long, gcc seems to get it right.在 gcc、linux 和 x64 上:即使 f() 的 arguments 没有转换为 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
How?如何?
在Linux x64上,可变参数的参数被“提升”为64位值,因此无需在此平台上显式转换为64位值。
Three things are happening here:这里发生了三件事:
The C standard defines something called “ default argument promotion ” for variadic arguments. That means : C 标准为可变参数 arguments 定义了称为“ 默认参数提升”的东西。这 意味着:
float
, it is promoted to type double
prior to the function call.float
类型,它会在 function 调用之前提升为double
类型。signed char
or unsigned char
, signed short
or unsigned short
, enumerated type, or bit field is converted to either a signed int or an unsigned int using integral promotion.signed char
或unsigned char
、 signed short
或unsigned short
、枚举类型或位字段都使用整数提升转换为 signed int 或 unsigned int。 But beware: This is int
, which is 32bit on x64 linux. There is more happening:但要注意:这是
int
,它在 x64 linux 上是 32 位的。还有更多的事情发生:
The SysV AMD64 ABI defines how the arguments are then passed to the function in section 3.2.3. SysV AMD64 ABI在第 3.2.3 节中定义了 arguments 如何传递给 function。 What matters here is: The first 6 integers are passed in registers, the remaining ones are pushed to the stack.
这里重要的是:前 6 个整数在寄存器中传递,其余的被压入堆栈。 Also: “The size of each argument gets rounded up to eightbytes.
另外:“每个参数的大小四舍五入到八字节。 [...] Therefore the stack will always be eightbyte aligned”.
[...] 因此堆栈将始终是八字节对齐的”。 Note that floats are passed in the floating point registers, not the normal ones.
请注意,浮点数是在浮点寄存器中传递的,而不是普通寄存器。
Functions that contain va_start
get a special prologue that also pushes all the argument registers to the stack.包含
va_start
的函数有一个特殊的序言,该序言还将所有参数寄存器压入堆栈。 How this is done is not part of the calling convention.这是如何完成的不是调用约定的一部分。 Since the compiler doesn't know the actual argument sizes at this point, you can expect it to push the whole registers making these values also 64bit aligned.
由于此时编译器不知道实际参数大小,您可以预期它会压入整个寄存器,使这些值也是 64 位对齐的。
va_arg
then first iterates through these, then the rest of the arguments which were passed on the stack. va_arg
然后首先遍历这些,然后是传递到堆栈的 arguments 中的 rest。
All of this means that all the arguments are 64bit aligned.所有这些意味着所有 arguments 都是 64 位对齐的。 But they are not actually 64bit wide.
但它们实际上并不是 64 位宽的。 All integers are at least 32bit wide (
_Bool
/ bool
is 8bit wide), but nothing more.所有整数至少为 32 位宽(
_Bool
/ bool
为 8 位宽),仅此而已。 The value of the unused bits is unspecified.未使用的位的值未指定。 The caller is free to leave these bits uninitialized.
调用者可以自由地保留这些位未初始化。 Thus:
因此:
The essential bit of code which makes it work is 使之起作用的基本代码是
x = va_arg(ap, long);
You could shoot yourself in the foot quite well by changing it to any other type. 通过将其更改为任何其他类型,您可以很好地射击自己的脚。
char ch = va_arg(ap, char);
Depending on the rules of the target architecture, this might increment ap
by one, two, four, or eight after each access. 根据目标体系结构的规则,这可能在每次访问后将
ap
增加ap
或8。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.