C语言: how memory alignment happened in the stack for array

标签 c x86 gdb stack memory-alignment

所有,我有一个关于 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来检查内存,

%ebpefe8

%espefd8

&buf1efe3

&buf2efdb

在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

现在我的问题是:

  1. 为什么字符“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/

    相关文章:

    c - 使用C语言在自定义shell中进行箭头键控制

    CodeWarrior 编译器警告 : "Possible loss of data"

    c++ - 对不同的整数宽度使用 xadd

    c++ - 在需要 sudo 运行的 NetBeans 8.0 中调试 C++ 程序

    更改动态 *char 的大小

    c - 在 Linux 平台上以编程方式获取 USB 设备的供应商 ID、产品 ID

    assembly - 是否有 `x86` 指令来告诉该指令正在哪个核心上运行?

    c - 加载指令中的 AVX2 __m256i const* mem_addr 与 AVX

    gdb 在特定线程中禁用断点

    GDB 断点