了解基指针和堆栈指针:在gcc输出的上下文中

我有以下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? 几乎看起来arrays上的元素按相反顺序放置。

另外为什么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]; } 

这不会给堆栈带来更多洞察力。 但是使用稍微不同的语义来查看汇编的差异输出是很有趣的。

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

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

堆栈布局

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

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

很少有一个大的初始化数组在堆栈框架中的正确位置,你可以使用一系列的推动,所以gcc没有被教导这样做。 (更详细地说:数组初始化作为块内存副本处理,它作为一系列移动指令或对memcpy的调用发出,具体取决于它的大小。决定发出什么的代码不知道块在内存中的位置,所以它不知道它是否可以使用push 。)

而且, movl更快。 具体来说, push执行%esp的隐式读取 – 修改 – 写入,因此必须按顺序执行push序列。 相反, movl到独立地址可以并行执行。 因此,通过使用一系列movl而不是push es,gcc为CPU提供了更多的指令级并行性。

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

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

-Os

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

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

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

 %rbp <-- Highest memory address -12 -16 -20 -24 -28 -32 -36 -40 -44 -48 <-- Address of array