根据 AMD64 ABI,什么样的 C11 数据类型是数组

What kind of C11 data type is an array according to the AMD64 ABI

我正在研究在 OSX 上使用的 x86_64 的调用约定,并且正在阅读 the System V x86-64 ABI standard 中名为 "Aggregates and Unions" 的部分。它提到了数组,我认为这就像一个固定长度的 c 数组,例如int[5].

我转到“3.2.3 参数传递”以了解数组是如何传递的,如果我理解正确的话,像 uint8_t[3] 这样的东西应该在寄存器中传递,因为它小于四个八字节聚合类型分类规则 1 施加的限制(第 18 页底部附近)。

编译后我发现它是作为指针传递的。 (我在 OSX 10.11.6 上使用来自 Xcode 7.3.1 的 clang-703.0.31 进行编译)。

我用来编译的例子源码如下:

#include <stdio.h>

#define type char

extern void doit(const type[3]);
extern void doitt(const type[5]);
extern void doittt(const type[16]);
extern void doitttt(const type[32]);
extern void doittttt(const type[40]);

int main(int argc, const char *argv[]) {
  const char a[3] = { 1, 2, 3 };
  const char b[5] = { 1, 2, 3, 4, 5 };
  const char c[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 };
  const char d[32] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 };
  const char e[40] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };

  doit(a);
  doitt(b);
  doittt(c);
  doitttt(d);
  doittttt(e);
}

我将其转储到名为 a.c 的文件中并使用以下命令进行编译:clang -c a.c -o a.o。我使用 otool 分析生成的程序集(由 运行 otool -tV a.o)并得到以下输出:

a.o:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    subq    [=11=]x10, %rsp
0000000000000008    leaq    _main.a(%rip), %rax
000000000000000f    movl    %edi, -0x4(%rbp)
0000000000000012    movq    %rsi, -0x10(%rbp)
0000000000000016    movq    %rax, %rdi
0000000000000019    callq   _doit
000000000000001e    leaq    _main.b(%rip), %rdi
0000000000000025    callq   _doitt
000000000000002a    leaq    _main.c(%rip), %rdi
0000000000000031    callq   _doittt
0000000000000036    leaq    _main.d(%rip), %rdi
000000000000003d    callq   _doitttt
0000000000000042    leaq    _main.e(%rip), %rdi
0000000000000049    callq   _doittttt
000000000000004e    xorl    %eax, %eax
0000000000000050    addq    [=11=]x10, %rsp
0000000000000054    popq    %rbp
0000000000000055    retq

或者等价地,它在 Godbolt compiler explorer with clang3.7 上,目标是使用相同 ABI 的 Linux。


所以,我想知道是否有人可以引导我了解 C11 中的哪些数据类型适用于数组。 (看起来 clang 默认使用 C11 - 请参阅 C99 内联函数下方的简介 here)。

我也对 ARM 进行了类似的调查并发现了类似的结果,尽管 ARM standard 还指定存在数组聚合类型

另外,在某些标准中是否有规定将固定长度的数组视为指针?

Bare 数组作为 C 和 C++ 中的函数参数 总是 衰减为指针,就像在其他几个上下文中一样。

structs 或 unions 中的数组没有,并且按值传递。这就是为什么 ABI 需要关心它们是如何传递的,即使在 C 中对于裸数组它不会发生。


作为, the relevant part of the C standard is N1570 section 6.7.6.3 paragraph 7

A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation ... (stuff about foo[static 10], see below)

请注意,多维数组作为数组类型的数组工作,因此只有最外层的“数组性”转换为指向数组类型的指针。


术语:x86-64 ABI 文档使用与 ARM 相同的术语,其中 structs 和数组是“聚合”(顺序地址处的多个元素)。所以短语“聚合和联合”经常出现,因为 unions 由语言和 ABI 类似地处理。

正是处理复合类型 (struct/union/class) 的递归规则使 ABI 中的数组传递规则发挥作用。 对于 C 或 C++,这是您将看到将数组复制到堆栈作为函数 arg 的一部分的唯一方式

struct s { int a[8]; };
void ext(struct s byval);

void foo() { struct s tmp = {{0}}; ext(tmp); }

gcc6.1 compiles it (for the AMD64 SysV ABI, with -O3) 到以下内容:

    sub     rsp, 40    # align the stack and leave room for `tmp` even though it's never stored?
    push    0
    push    0
    push    0
    push    0
    call    ext
    add     rsp, 72
    ret

在 x86-64 ABI 中,按值传递是通过实际复制(到寄存器或堆栈)而不是通过隐藏指针进行的。

请注意,当 return 值太大而无法放入时,return-by-value 确实将指针作为“隐藏”第一个参数(在 rdi 中)传递rdx:rax 的 128 位连接(并且不是在向量 regs 等中 returned 的向量)

ABI 可以使用隐藏指针指向超过一定大小的按值传递对象,并相信被调用的函数不会修改原始函数,但这不是 x86-64 ABI 选择的去做。这在某些情况下会更好(特别是对于低效的 C++,有大量未经修改的复制(即浪费)),但在其他情况下更糟。

SysV ABI 加分阅读:作为 tag wiki points out, the current version of the ABI standard doesn't fully document the behaviour that compilers rely on: .


请注意,要真正保证函数 arg 是固定大小的数组,C99 and later lets you use the static keyword in a new way: 关于数组大小。 (当然,它仍然作为指针传递。这不会改变 ABI)。

void bar(int arr[static 10]);

这允许编译器发出有关越界的警告。如果编译器知道它被允许访问 C 源代码不允许的元素,它也可能实现更好的优化。 (参见 this blog post)。但是,arg 仍然具有类型 int*,而不是实际数组,因此 sizeof(arr) == sizeof(int*).

The same keyword page for C++表示ISO C++不支持static的这种用法;它是 C 专用的另一个功能,以及 C99 可变长度数组和 C++ 没有的其他一些好东西。

在 C++ 中,您可以使用 std::array<int,10> 获取传递给调用方的编译时大小信息。但是,如果需要的话,您必须通过引用手动传递它,因为它当然只是一个包含 int arr[10] 的 class。 与 C 风格的数组不同,它不会自动衰减到 T*


您链接的 ARM 文档 似乎实际上并未将数组称为聚合类型:第 4.3 节复合类型(讨论alignment) 将数组与聚合类型区分开来,即使它们看起来是聚合定义的特例。

A Composite Type is a collection of one or more Fundamental Data Types that are handled as a single entity at the procedure call level. A Composite Type can be any of:

  • An aggregate, where the members are laid out sequentially in memory
  • A union, where each of the members has the same address
  • An array, which is a repeated sequence of some other type (its base type).

The definitions are recursive; that is, each of the types may contain a Composite Type as a member

“复合”是一个涵盖性术语,包括数组、结构和联合。