所有,我有一个关于 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 0xbffefd8
,它显示
0xbfffefd8: 9 -124 4 98
如果我运行 x/bd 0xbffffefd8,它会显示
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 字节单词的开头。另外,如果我继续向从efdb
开始的buf2
填充更多的'b',它只能填充5'b',而不是8。为什么呢?那'\0'呢?
同样的事情发生在buf1
上,它从efe3
开始,而不是efe0
。这是一种什么样的对齐方式?这对我来说没有意义。
- 从汇编代码来看,它没有显示我从其他地方看到的 16 对齐,就像这样,
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”,而仅使用了第二个双字的 1 个字节。现在,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
到您的 array3[2]
有 1 个字节的垃圾。
编译器绝对允许这样做。如果您希望您的 char
数组以 4 字节对齐(但您需要一个充分的理由!),您必须使用特殊的编译器属性,例如:
__attribute__ ((aligned(4)))
关于C语言: how memory alignment happened in the stack for array,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59865199/