c - gcc (x64) 如何处理可变参数函数中的类型/大小?

标签 c gcc variadic

可变参数函数和 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 charunsigned charsignedshortunsignedshort、枚举类型或位字段使用积分提升将其转换为有符号整型或无符号整型。

但要注意:这是 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/

相关文章:

C++0x 将参数传递给可变参数模板函数

c - GnuTLS:编译示例代码时出错?

c++ - 代码块不正确的 c++ 输出

c++ - AVX2 的汇编错误

c - getopt、optarg 和可选参数

c++ - 从 C++ 宏创建字符串列表和枚举列表

c - 如何编写 C 函数接受(一个)任何类型的参数

c - 递增 struct* 中 int* 指向的值

c - 我的 makefile 有什么问题?

c - ARM 未对齐内存访问解决方法