堆栈上的数组内存分配
Arrays memory allocation on stack
我在 C:
中有 2 个函数
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 , -64(%rbp)
movb , -15(%rbp)
nop
popq %rbp
ret
func2(unsigned char):
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
movb %al, -84(%rbp)
movb , -64(%rbp)
movb , -15(%rbp)
movb , -74(%rbp)
movb , -65(%rbp)
nop
popq %rbp
ret
我可以看到,对于 50 个字节的数组,分配了 64 个字节。它似乎在 16 字节边界上分配堆栈,因为对于 10 字节 - 分配了 16 字节。
我的问题:
1) 数组的 16 字节边界是否有一些堆栈对齐标准?
导致像 int、long int 等变量显然没有分配在 16 字节边界上。
2) 为什么数组分配方式如此奇怪?我们可以看到数组 a
、14 字节 被添加为对齐填充 紧随 我们的有效负载 50 bytes,但对于数组 b
,6 个对齐字节 被分配 before 我们 的有效负载10 个字节。我错过了什么吗?
3) 为什么函数参数在 EDI
(unsigned char x
) 中传递,放置在距数组内存开始(包括填充)4 个字节的堆栈中。所以字节变量(AL register
)也被填充了还是什么?为什么 4 字节 ?
x86_64 abi 要求 16 字节堆栈对齐(进入函数时堆栈指针必须 16 字节对齐)。但是你看到的过度对齐是-O0
造成的;使用 -O1
或更高,数组对齐更有效。例如
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 [=11=]x1,-0x40(%rsp)
15: c6 44 24 f1 02 movb [=11=]x2,-0xf(%rsp)
1a: c6 44 24 b6 04 movb [=11=]x4,-0x4a(%rsp)
1f: c6 44 24 bf 05 movb [=11=]x5,-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
创建其他布局。
我在 C:
中有 2 个函数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 , -64(%rbp)
movb , -15(%rbp)
nop
popq %rbp
ret
func2(unsigned char):
pushq %rbp
movq %rsp, %rbp
movl %edi, %eax
movb %al, -84(%rbp)
movb , -64(%rbp)
movb , -15(%rbp)
movb , -74(%rbp)
movb , -65(%rbp)
nop
popq %rbp
ret
我可以看到,对于 50 个字节的数组,分配了 64 个字节。它似乎在 16 字节边界上分配堆栈,因为对于 10 字节 - 分配了 16 字节。
我的问题:
1) 数组的 16 字节边界是否有一些堆栈对齐标准? 导致像 int、long int 等变量显然没有分配在 16 字节边界上。
2) 为什么数组分配方式如此奇怪?我们可以看到数组 a
、14 字节 被添加为对齐填充 紧随 我们的有效负载 50 bytes,但对于数组 b
,6 个对齐字节 被分配 before 我们 的有效负载10 个字节。我错过了什么吗?
3) 为什么函数参数在 EDI
(unsigned char x
) 中传递,放置在距数组内存开始(包括填充)4 个字节的堆栈中。所以字节变量(AL register
)也被填充了还是什么?为什么 4 字节 ?
x86_64 abi 要求 16 字节堆栈对齐(进入函数时堆栈指针必须 16 字节对齐)。但是你看到的过度对齐是-O0
造成的;使用 -O1
或更高,数组对齐更有效。例如
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 [=11=]x1,-0x40(%rsp)
15: c6 44 24 f1 02 movb [=11=]x2,-0xf(%rsp)
1a: c6 44 24 b6 04 movb [=11=]x4,-0x4a(%rsp)
1f: c6 44 24 bf 05 movb [=11=]x5,-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
创建其他布局。