gcc(x64)如何处理变量函数中的类型/大小?

anauzrmj  于 2022-11-13  发布在  其他
关注(0)|答案(3)|浏览(125)

变元函数和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

怎么做?

djp7away

djp7away1#

在linux x64上,变量函数的参数被“提升”为64位值,因此在此平台上不需要显式转换为64位值。

ipakzgxi

ipakzgxi2#

这里发生了三件事:

1.缺省参数提升

C标准为变量参数定义了一个名为“default argument promotion“的东西。

  • 如果实际参数的类型为float,则在函数调用之前将其提升为double类型。
  • 任何signed charunsigned charsigned shortunsigned 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位宽),仅此而已。未使用位的值是未指定的。调用者可以自由地将这些位保留为未初始化。因此:

  • 负值(godbold)的代码被破坏
  • 该规范并不保证垃圾数据不会在将来的某个时候出现在高位,但这似乎是不太可能的,因为很多代码可能依赖于这种行为(见注解)。
vdzxcuhz

vdzxcuhz3#

使其工作的基本代码是

x = va_arg(ap, long);

你可以把它换成任何其他类型,很好地击中自己的脚。

char ch = va_arg(ap, char);

根据目标体系结构的规则,每次访问后ap可能会增加1、2、4或8。

相关问题