C语言:数组的堆栈中内存对齐是如何发生的
C language: how memory alignment happened in the stack for array
所有,我有一个关于 C 中数组内存对齐的有趣问题。我的 OS 是 32 位 Ubuntu,我用 gcc -S -fno-stack-protector 选项编译它。
代码:
char array1[5] = "aaaaa";
char array2[8];
array2[0] = 'b';
汇编代码:
pushl %ebp
move %esp, %ebp. # esp and ebp are pointing to the same words
subl , %esp # move esp to lower 16
movl 33771873, -5(%ebp) # input "aaaa"
movb , -1(%ebp). # input 'a'
movb , -13(%ebp) # input 'b'
movl [=11=], %eax
leave
我有 GDB 检查内存,
%ebp
是 efe8
、
%esp
是 efd8
,
&buf1
是 efe3
,
&buf2
是 efdb
.
在GDB中,我运行x/4bd 0xbfffefd8
,它显示
0xbfffefd8: 9 -124 4 98
如果我运行 x/bd 0xbffffed8,它显示
0xbfffefd8: 9
如果我运行 x/bd 0xbffffefdb,它显示
0xbfffefd8: 98
所以记忆是这样的
## high address ##
? efe8 <-- ebb
97 97 97 97 efe4
0 -80 -5 97(a) efe0
0 0 0 0 efdc
9 -124 4 98(b) efd8 <-- esp
^ ^
| |
efd8 efdb
现在我的问题是:
- 为什么字符'b'(98)在
efdb
,而%esp
在efd8
?我认为 'b' 也应该在 efd8
,因为它是 4 字节字的开始。另外,如果我一直往'b'填到buf2
,从efdb
开始,只能填5'b',不能填8,怎么会呢?那么'\0'呢?
同样的事情发生在 buf1
,它从 efe3
开始,而不是 efe0
。这是一种怎样的对齐方式?这对我来说没有意义。
- 从汇编代码来看,没有显示16位对齐,我在其他地方看到的,像这样,
andl $-16, %esp # this aligns esp to 16 boundary
andl 命令什么时候显示,什么时候不显示?它很常见,所以我希望在每个节目中都能看到它。
从上面的汇编代码中,我看不出内存对齐。总是如此吗?我的理解是,汇编代码只是将高级代码(非常可读)解释为不太可读的代码,但仍然转换了确切的消息,因此 char[5]
没有被解释为考虑内存对齐的方式。那么内存对齐应该发生在运行ning时间。我对吗?但是GDB调试显示的和汇编代码完全一样。根本没有对齐。
谢谢。
我看不出有什么问题。 TLDR 答案:char 数组对齐到 1 个字节,编译器是正确的。
再深入一点。在我的 64 位机器上,使用带有 -m32 选项的 GCC 7,我 运行 调试了相同的代码,我得到了相同的结果:
(gdb) x/4bd $esp+12
0xffffcdd4: 97 97 97 97
(gdb) x/4bd $esp+8
0xffffcdd0: 0 -48 -7 97
(gdb) x/4bd $esp+4
0xffffcdcc: 0 0 0 0
(gdb) x/4bd $esp+0
0xffffcdc8: 41 85 85 98
地址当然不同,这没关系。现在,让我试着解释一下。
首先,$esp
,按预期对齐 4 字节:
(gdb) p $esp
= (void *) 0xffffcdc8
到目前为止,还不错。现在,因为我们知道 char 数组默认使用 1 作为对齐方式,所以让我们试着弄清楚编译时发生了什么。首先,编译器看到 array1[5]
并将其放入堆栈,但因为它有 5 个字节宽,所以它已将其扩展到第二个双字。因此,第一个双字充满了 'a' 而第二个双字仅使用了一个字节。现在,array2[8]
紧跟在 array1[5]
之后(或之前,取决于您的看法)。它扩展到 3 个双字,以 $esp
.
指向的双字结束
所以,我们有:
[esp + 0] <3 bytes of garbage /* no var */>, 'b' /* array2 */,
[esp + 4] 0x0, 0x0, 0x0, 0x0, /* still array2 */
[esp + 8] <3 bytes of garbage /* still array2 */>, 'a' /* array1 */,
[esp + 12] 'a', 'a', 'a', 'a', /* still array1 */.
如果你在 array2
之后添加一个 char[2]
数组,你会看到它使用 $esp
指向的相同双字并且仍然有来自 $esp
的 1 字节垃圾到你的 array3[2]
.
编译器绝对允许这样做。如果您希望 char
数组以 4 字节对齐(但您需要一个 good 理由!),您必须使用特殊的编译器属性,例如:
__attribute__ ((aligned(4)))
所有,我有一个关于 C 中数组内存对齐的有趣问题。我的 OS 是 32 位 Ubuntu,我用 gcc -S -fno-stack-protector 选项编译它。
代码:
char array1[5] = "aaaaa";
char array2[8];
array2[0] = 'b';
汇编代码:
pushl %ebp
move %esp, %ebp. # esp and ebp are pointing to the same words
subl , %esp # move esp to lower 16
movl 33771873, -5(%ebp) # input "aaaa"
movb , -1(%ebp). # input 'a'
movb , -13(%ebp) # input 'b'
movl [=11=], %eax
leave
我有 GDB 检查内存,
%ebp
是 efe8
、
%esp
是 efd8
,
&buf1
是 efe3
,
&buf2
是 efdb
.
在GDB中,我运行x/4bd 0xbfffefd8
,它显示
0xbfffefd8: 9 -124 4 98
如果我运行 x/bd 0xbffffed8,它显示
0xbfffefd8: 9
如果我运行 x/bd 0xbffffefdb,它显示
0xbfffefd8: 98
所以记忆是这样的
## high address ##
? efe8 <-- ebb
97 97 97 97 efe4
0 -80 -5 97(a) efe0
0 0 0 0 efdc
9 -124 4 98(b) efd8 <-- esp
^ ^
| |
efd8 efdb
现在我的问题是:
- 为什么字符'b'(98)在
efdb
,而%esp
在efd8
?我认为 'b' 也应该在efd8
,因为它是 4 字节字的开始。另外,如果我一直往'b'填到buf2
,从efdb
开始,只能填5'b',不能填8,怎么会呢?那么'\0'呢?
同样的事情发生在 buf1
,它从 efe3
开始,而不是 efe0
。这是一种怎样的对齐方式?这对我来说没有意义。
- 从汇编代码来看,没有显示16位对齐,我在其他地方看到的,像这样,
andl $-16, %esp # this aligns esp to 16 boundary
andl 命令什么时候显示,什么时候不显示?它很常见,所以我希望在每个节目中都能看到它。
从上面的汇编代码中,我看不出内存对齐。总是如此吗?我的理解是,汇编代码只是将高级代码(非常可读)解释为不太可读的代码,但仍然转换了确切的消息,因此 char[5]
没有被解释为考虑内存对齐的方式。那么内存对齐应该发生在运行ning时间。我对吗?但是GDB调试显示的和汇编代码完全一样。根本没有对齐。
谢谢。
我看不出有什么问题。 TLDR 答案:char 数组对齐到 1 个字节,编译器是正确的。
再深入一点。在我的 64 位机器上,使用带有 -m32 选项的 GCC 7,我 运行 调试了相同的代码,我得到了相同的结果:
(gdb) x/4bd $esp+12
0xffffcdd4: 97 97 97 97
(gdb) x/4bd $esp+8
0xffffcdd0: 0 -48 -7 97
(gdb) x/4bd $esp+4
0xffffcdcc: 0 0 0 0
(gdb) x/4bd $esp+0
0xffffcdc8: 41 85 85 98
地址当然不同,这没关系。现在,让我试着解释一下。
首先,$esp
,按预期对齐 4 字节:
(gdb) p $esp
= (void *) 0xffffcdc8
到目前为止,还不错。现在,因为我们知道 char 数组默认使用 1 作为对齐方式,所以让我们试着弄清楚编译时发生了什么。首先,编译器看到 array1[5]
并将其放入堆栈,但因为它有 5 个字节宽,所以它已将其扩展到第二个双字。因此,第一个双字充满了 'a' 而第二个双字仅使用了一个字节。现在,array2[8]
紧跟在 array1[5]
之后(或之前,取决于您的看法)。它扩展到 3 个双字,以 $esp
.
所以,我们有:
[esp + 0] <3 bytes of garbage /* no var */>, 'b' /* array2 */,
[esp + 4] 0x0, 0x0, 0x0, 0x0, /* still array2 */
[esp + 8] <3 bytes of garbage /* still array2 */>, 'a' /* array1 */,
[esp + 12] 'a', 'a', 'a', 'a', /* still array1 */.
如果你在 array2
之后添加一个 char[2]
数组,你会看到它使用 $esp
指向的相同双字并且仍然有来自 $esp
的 1 字节垃圾到你的 array3[2]
.
编译器绝对允许这样做。如果您希望 char
数组以 4 字节对齐(但您需要一个 good 理由!),您必须使用特殊的编译器属性,例如:
__attribute__ ((aligned(4)))