全部,我对C语言中的数组的内存对齐有一个有趣的问题。我的操作系统是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 $16, %esp # move esp to lower 16
movl $1633771873, -5(%ebp) # input "aaaa"
movb $97, -1(%ebp). # input 'a'
movb $98, -13(%ebp) # input 'b'
movl $0, %eax
leave
我有GDB检查内存,
[%ebp
是efe8
,
[%esp
是efd8
,
[&buf1
是efe3
,
[&buf2
是efdb
。
在GDB中,我运行x/4bd 0xbfffefd8
,它显示
0xbfffefd8: 9 -124 4 98
如果我运行x / bd 0xbfffefd8,则显示
0xbfffefd8: 9
如果我运行x / bd 0xbfffefdb,则显示
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
现在我的问题是:
efdb
,而%esp
在efd8
?我认为“ b”也应位于efd8
,因为它是4字节字的开头。此外,如果我继续从buf2
开始向efdb
填充更多的'b',它只能填充5'b',而不能填充8。为什么呢?那'\ 0'呢? buf1
发生了相同的事情,它从efe3
开始,而不是efe0
。这是什么对齐方式?这对我来说没有意义。
andl $-16, %esp # this aligns esp to 16 boundary
何时显示[[andl命令,何时不显示?这很常见,所以我希望在每个程序中都能看到它。
从上面的汇编代码中,我看不到内存对齐。总是这样吗?我的理解是,汇编代码只是将高级代码(非常可读)解释为非常不可读的代码,但仍会转换准确的消息,因此char[5]
不会解释为考虑内存对齐的方式。然后,内存对齐应在运行时发生。我对吗?但是GDB调试显示的代码与汇编代码完全相同。完全不对齐。谢谢。
TLDR答案:char数组对齐到1个字节,编译器正确。
进一步挖掘。在我的64位计算机上,将GCC 7与-m32选项一起使用,我运行并调试了相同的代码,获得了相同的结果:(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 $9 = (void *) 0xffffcdc8
到目前为止,很好。现在,由于我们知道char数组默认情况下使用作为对齐方式,因此让我们尝试弄清楚在编译时发生了什么。首先,编译器看到1
array1[5]
并将其放在堆栈上,但是由于它是5字节宽,因此将其扩展到了第二个双字。因此,第一个双字充满了“ a”,而仅使用了第二个双字的一个字节。现在,将array2[8]
放置在array1[5]
之后(或之前,具体取决于您的外观)。它在3个双字上扩展,以$esp
指向的双字结尾。所以,我们有:[esp + 0] <3 bytes of garbage>, 'b',
[esp + 4] 0, 0, 0, 0,
[esp + 8] <3 bytes of garbage>, 'a',
[esp + 12] <4 times 'a'>.
如果在char[2]
之后添加array2
数组,您将使用$esp
指向的相同双字看到它,并且从$esp
到array3[2]
仍有1个字节的垃圾。绝对允许编译器执行此操作。如果希望将char数组对齐为4字节(但是需要
good原因!),则必须使用特殊的编译器属性,例如:
__attribute__ ((aligned( value )))