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.7的Godbolt编译器浏览器,它针对使用相同ABI的Linux.
所以,我想知道是否有人可以引导我将C11中的数据类型应用于数组.(它看起来像铛默认使用C11 -见Blurb的位置向右下C99内联函数).
我也做了与ARM类似的调查,发现了类似的结果,即使在ARM标准还规定存在一个数组聚合类型.
另外,在某些标准的某处是否指定将固定长度数组视为指针?
作为C和C++中的函数arg的裸阵列总是衰减到指针,就像在其他几个上下文中一样.
struct
s或union
s中的数组不会,并按值传递.这就是为什么ABI需要关心它们如何通过的原因,即使在裸阵列的C中也不会发生这种情况.
正如Keith Thomson指出的那样,C标准的相关部分是N1570第6.7.6.3节第7段
参数声明为"数组类型"应调整为"限定指向类型的指针",其中类型限定符(如果有)是在数组类型派生的[和]中指定的那些... (关于
foo[static 10]
,见下文)
请注意,多维数组作为数组类型的数组工作,因此只有"array-ness"的最外层级别才会转换为指向数组类型的指针.
术语:x86-64 ABI doc使用与ARM相同的术语,其中struct
s和数组是"聚合"(连续地址处的多个元素).因此,"聚合和联合"这个短语出现了很多,因为union
s语言和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奖金阅读:正如x86标签维基指出的那样,当前版本的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"是一个包含数组,结构和联合的总称.