我在C中有两个函数:
void func1(unsigned char x)
{
unsigned char a[10][5];
a[0][0] = 1;
a[9][4] = 2;
}
void func2(unsigned char x)
{
unsigned char a[10][5];
a[0][0] = 1;
a[9][4] = 2;
unsigned char b[10];
b[0] = 4;
b[9] = 5;
}
编译:
gcc 7.3 x86-64
-O0 -g
OS:
16.04.1-Ubuntu x86-64
制作英特尔功能组合:
func1(unsigned char):
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
movb %al, -68(%rbp)
movb $1, -64(%rbp)
movb $2, -15(%rbp)
nop
popq %rbp
ret
func2(unsigned char):
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
movb %al, -84(%rbp)
movb $1, -64(%rbp)
movb $2, -15(%rbp)
movb $4, -74(%rbp)
movb $5, -65(%rbp)
nop
popq %rbp
ret
我可以看到,对于50字节的数组,分配了64个字节。似乎它在16字节边界上分配堆栈,因为对于10个字节 - 分配了16个字节。
我的问题:
1)数组的16字节边界是否有一些标准的堆栈对齐? int,long int等原因变量显然不能在16字节边界上分配。
2)为什么数组以这种奇怪的方式分配?我们可以看到数组a
, 14个字节在 50字节的有效负载之后添加为对齐填充,但是数组b
, 6对齐字节在 10字节的有效负载之前分配。我错过了什么吗?
3)为什么函数参数在EDI
(unsigned char x
)中传递,放置在离我们的数组内存开始的4个字节的堆栈(包括填充)。那么字节变量(AL register
)也是填充的还是什么?为什么 4个字节?
答案 0 :(得分:4)
x86_64 abi需要16字节堆栈对齐(进入函数时堆栈指针必须对齐16字节)。但是你看到的那个人是由-O0
引起的;使用-O1
或更高版本,阵列可以更有效地对齐。 E.g。
void func2(unsigned char x)
{
unsigned char a[10][5];
a[0][0] = 1;
a[9][4] = 2;
unsigned char b[10];
b[0] = 4;
b[9] = 5;
__asm__ __volatile__("" :: "r"(a), "r"(b) : "memory");
}
导致:
gcc -O1 -g x.c -c
objdump -d x.o
0000000000000010 <func2>:
10: c6 44 24 c0 01 movb $0x1,-0x40(%rsp)
15: c6 44 24 f1 02 movb $0x2,-0xf(%rsp)
1a: c6 44 24 b6 04 movb $0x4,-0x4a(%rsp)
1f: c6 44 24 bf 05 movb $0x5,-0x41(%rsp)
24: 48 8d 44 24 c0 lea -0x40(%rsp),%rax
29: 48 8d 54 24 b6 lea -0x4a(%rsp),%rdx
2e: c3 retq
-Os
或-O3
创建其他布局。