12

我有以下 C 程序:

int main()
{
    int c[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};
    return c[0];
}

当使用带有 gcc 的 -S 指令编译时,我得到以下程序集:

    .file   "array.c"
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    $0, -48(%rbp)
    movl    $0, -44(%rbp)
    movl    $0, -40(%rbp)
    movl    $0, -36(%rbp)
    movl    $0, -32(%rbp)
    movl    $0, -28(%rbp)
    movl    $0, -24(%rbp)
    movl    $0, -20(%rbp)
    movl    $1, -16(%rbp)
    movl    $2, -12(%rbp)
    movl    -48(%rbp), %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
    .section        .note.GNU-stack,"",@progbits

我不明白的是为什么较早的数组元素离 bp 更远?看起来数组上的元素几乎是以相反的顺序放置的。

另外为什么 gcc 不使用 push 而不是 movl 将数组元素推入堆栈?


不同的观点

将数组作为静态变量移动到全局命名空间到我得到的模块:

    .file   "array.c"
    .data
    .align 32
    .type   c, @object
    .size   c, 40
c:
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   0
    .long   1
    .long   2
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    c(%rip), %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.5 20110214 (Red Hat 4.4.5-6)"
    .section    .note.GNU-stack,"",@progbits

使用以下 C 程序:

static int c[10] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2};

int main() 
{
    return c[0];
}

这并没有给堆栈提供更多的洞察力。但是看到汇编的不同输出使用略有不同的语义是很有趣的。

4

3 回答 3

9

首先,x86 堆栈向下增长。按照惯例,rbp存储 的原始值rsp。因此,函数的参数位于相对于 的偏移处rbp,其自动变量位于偏移处。自动数组的第一个元素的地址低于所有其他元素,因此离rbp.

这是此页面上显示的方便图表:

堆栈布局

我看不出编译器为什么不能使用一系列push指令来初始化你的数组。这是否是一个好主意,我不确定。

于 2011-12-12T17:52:09.217 回答
3

另外为什么 gcc 不使用 push 而不是 movl 将数组元素推入堆栈?

在堆栈帧的正确位置有一个大的初始化数组是非常罕见的,您可以使用一系列推送,因此没有教 gcc 这样做。(更详细地说:数组初始化作为块内存副本处理,它作为移动指令序列或对 的调用发出memcpy,具体取决于它有多大。决定发出什么的代码不知道在哪里在内存中该块正在运行,因此它不知道是否可以使用它push。)

而且,movl速度更快。具体来说,push是对 进行隐式读-修改-写%esp,因此push必须按顺序执行一系列 es。 movl相比之下,独立地址可以并行执行。因此,通过使用movls 序列而不是pushes,gcc 为 CPU 提供了更多的指令级并行性来利用。

请注意,如果我在激活任何级别的优化的情况下编译您的代码,则该数组将完全消失!这是-O1(这是在objdump -dr目标文件上运行的结果,而不是-S输出,所以你可以看到实际的机器代码)

0000000000000000 <main>:
   0:   b8 00 00 00 00          mov    $0x0,%eax
   5:   c3                      retq   

-Os

0000000000000000 <main>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq   

什么都不做总是比做某事快。清除一个寄存器xor是两个字节而不是五个,但对寄存器的旧内容有正式的数据依赖性并修改条件代码,因此可能会更慢,因此仅在优化大小时才选择。

于 2011-12-12T18:19:36.010 回答
2

请记住,在 x86 上,堆栈向下增长。压入堆栈将从堆栈指针中减去。

%rbp <-- Highest memory address
-12
-16
-20
-24
-28
-32
-36
-40
-44
-48  <-- Address of array
于 2011-12-12T17:51:57.847 回答