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

我正在研究在 OSX 上使用的 x86_64 的调用约定,并且正在阅读 the System V x86-64 ABI standard 中名为“聚合和 union ”的部分。 ).它提到了数组,我认为这就像一个固定长度的 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    $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

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


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

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

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

最佳答案

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

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


作为Keith Thomson points out ,C标准的相关部分是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 相同的术语,其中 struct s 和数组是“聚合”(连续地址处的多个元素)。所以短语“聚合和 union ”经常出现,因为union s 由语言和 ABI 类似地处理。

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

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 中,按值传递是通过实际复制(到寄存器或堆栈)而不是通过隐藏指针进行的。

请注意,当返回值太大而不适合 rdi 的 128 位串联时,按值返回确实将指针作为“隐藏”的第一个参数(在 rdx:rax 中)传递。 (并且不是在 vector regs 等中返回的 vector )

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

SysV ABI 额外阅读:作为 x86 tag wiki 指出,当前版本的 ABI 标准并未完整记录编译器所依赖的行为:clang/gcc sign/zero extend narrow args to 32bit .


请注意,要真正保证函数 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] 的类。 . 与 C 风格的数组不同,它不会衰减到 T*自动。


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

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

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

https://stackoverflow.com/questions/38800044/

相关文章:

python - 如何从递归 Python 函数返回值?

file - 在同一区域将大文件从 S3 下载到 EC2 的最快方法是什么?

sql - 计算 pl/sql 中游标的行数

php - 如何使用php将html代码转换为png图像

haskell - 让 Multiline haskell 函数在 ghci 中工作

maven - 缺少神器 soapui :SoapUI:jar:5. 2.1

amazon-web-services - 将记录写入 Aurora 数据库实例时触发 AWS La

r - 按组移动 data.table 中的一列列表

elixir - 为什么 is_atom(nil) 在 elixir 中等于 true?

module - 如何导入本地模块?