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

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

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

编译后,我看到它被作为指针传递。 (我正在使用OSX 10.11.6上的Xcode 7.3.1中的clang-703.0.31进行编译)。

我用来编译的示例源如下:

 #include  #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); } 

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

 ao: (__TEXT,__text) section _main: 0000000000000000 pushq %rbp 0000000000000001 movq %rsp, %rbp 0000000000000004 subq $0x10, %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 $0x10, %rsp 0000000000000054 popq %rbp 0000000000000055 retq 

或者等效地,这里是使用clang3.7的Godbolt编译器浏览器 ,它针对使用相同ABI的Linux。


所以,我想知道是否有人可以引导我将C11中的数据类型应用于数组。 (看起来clang默认使用C11 – 请参阅C99内联函数下的blurb)。

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

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

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

struct s或union s中的数组不会,并且按值传递。 这就是为什么ABI需要关心它们如何通过,即使它在C中没有发生裸arrays。


正如Keith Thomson指出的那样 ,C标准的相关部分是N1570第6.7.6.3节第7段

参数声明为“数组类型”应调整为“限定指向类型的指针”,其中类型限定符(如果有)是在数组类型派生的[和]中指定的那些… (关于foo[static 10]东西) foo[static 10] ,见下文)

请注意,多维数组作为数组类型的数组工作,因此只有“array-ness”的最外层级别才会转换为指向数组类型的指针。


术语:x86-64 ABI doc使用与ARM相同的术语,其中struct和array是“聚合”(连续地址处的多个元素)。 因此,“聚合和联合”这个短语出现了很多,因为union的处理方式与语言和ABI类似。

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

 struct s { int a[8]; }; void ext(struct s byval); void foo() { struct s tmp = {{0}}; ext(tmp); } 

gcc6.1将它(对于AMD64 SysV ABI,带有-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中,pass-by-value通过实际复制(进入寄存器或堆栈)而不是隐藏指针进行。

请注意,当返回值太大而无法适应rdx:rax的128位并置(并且不是返回的向量)时,按值返回会将指针作为“隐藏”的第一个arg(在rdi )传递矢量注册等等)

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

SysV ABI奖金阅读 :正如x86标签维基指出的那样,当前版本的ABI标准并没有完全记录编译器所依赖的行为: clang / gcc sign / zero将narrow args扩展到32bit 。


请注意,为了确保函数arg是固定大小的数组, C99及更高版本允许您以新的方式使用static关键字 :在数组大小上。 (当然,它仍然作为指针传递。这不会改变ABI)。

 void bar(int arr[static 10]); 

这使得sizeof(arr)在调用函数内部可以正常工作,并允许编译器警告超出范围。 如果编译器知道允许访问C源不能访问的元素,它还可能实现更好的优化。 (见这篇博客文章 )。

C ++的相同关键字页面表明ISO C ++不支持static使用; 它是C-onlyfunction中的另一个,以及C99可变长度数组和C ++没有的其他一些好东西。

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


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

复合类型是一个或多个基本数据类型的集合,在过程调用级别作为单个实体处理。 复合类型可以是以下任何一种:

  • 聚合,其中成员按顺序排列在内存中
  • 一个联盟,每个成员都有相同的地址
  • 一个数组,是一些其他类型(其基类型)的重复序列。

这些定义是递归的; 也就是说,每种类型都可以包含复合类型作为成员

“复合”是一个包含数组,结构和联合的总称。