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

Dan*_*imm 2 c assembly types x86-64 calling-convention

我正在研究在OSX上使用的x86_64的调用约定,并且在System V x86-64 ABI标准中阅读了名为"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);
}
Run Code Online (Sandbox Code Playgroud)

我将其转储到一个名为的文件中,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    $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
Run Code Online (Sandbox Code Playgroud)

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


所以,我想知道是否有人可以引导我将C11中的数据类型应用于数组.(它看起来像铛默认使用C11 -见Blurb的位置向右下C99内联函数).

我也做了与ARM类似的调查,发现了类似的结果,即使ARM标准还规定存在一个数组聚合类型.

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

Pet*_*des 7

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

structs或unions中的数组不会,并按值传递.这就是为什么ABI需要关心它们如何通过的原因,即使在裸阵列的C中也不会发生这种情况.


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

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

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


术语:x86-64 ABI doc使用与ARM相同的术语,其中structs和数组是"聚合"(连续地址处的多个元素).因此,"聚合和联合"这个短语出现了很多,因为unions语言和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); }
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在x86-64 ABI中,pass-by-value通过实际复制(进入寄存器或堆栈)而不是隐藏指针进行.

请注意rdi,当返回值太大而无法适应128位串联rdx:rax(并且不是向量regs中返回的向量等)时,按值返回会将指针作为"隐藏"的第一个arg(in )传递.等)

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

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


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

void bar(int arr[static 10]);
Run Code Online (Sandbox Code Playgroud)

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

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

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


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

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

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

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

"Composite"是一个包含数组,结构和联合的总称.