鉴于以下最小测试用例:
void exit(int);
int main() {
exit(0);
}
GCC 4.9及更高版本的32位x86目标产生类似:
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
subl $12, %esp
pushl $0
call exit
请注意复杂的堆栈重新排列代码。然而,将函数重命名为除main之外的任何函数,它给出了(更合理):
xmain:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
subl $12, %esp
pushl $0
call exit
-O
的差异更加明显。由于main
没有任何变化;重命名,它产生:
xmain:
subl $24, %esp
pushl $0
call exit
在回答这个问题时注意到以上内容:
How do i get rid of call __x86.get_pc_thunk.ax
这种行为(及其动机)是否记录在案,是否有任何方法可以抑制它? GCC具有x86特定于目标的选项,用于设置首选/假定的传入和传出堆栈对齐以及启用/禁用任意函数的重新对齐,但它们似乎不适合main
。
答案 0 :(得分:6)
这个答案是基于源头潜水。我不知道开发者的意图或动机是什么。所有涉及的代码似乎都可以追溯到2008年,这是在我自己的GCC工作之后,但很久以前人们的记忆可能已经变得模糊了。 (GCC 4.9于2014年发布;你是否回过头了?如果我对这段代码的引入是正确的,main
的笨拙堆栈对齐应该在4.4版本中开始。)
GCC的x86后端似乎已被编码,以便在进入main
时对堆栈对齐做出超保守的假设,无论命令行选项如何。函数ix86_minimum_incoming_stack_boundary
被调用来计算每个函数的条目上的预期堆栈对齐,以及它做的最后一件事......
12523 /* Stack at entrance of main is aligned by runtime. We use the
12524 smallest incoming stack boundary. */
12525 if (incoming_stack_boundary > MAIN_STACK_BOUNDARY
12526 && DECL_NAME (current_function_decl)
12527 && MAIN_NAME_P (DECL_NAME (current_function_decl))
12528 && DECL_FILE_SCOPE_P (current_function_decl))
12529 incoming_stack_boundary = MAIN_STACK_BOUNDARY;
12530
12531 return incoming_stack_boundary;
如果正在编译的函数是MAIN_STACK_BOUNDARY
, ...会覆盖预期的堆栈对齐到保守常量main
。编译64位代码时MAIN_STACK_BOUNDARY
为128(位),编译32位代码时为main
。据我所知,没有命令行旋钮可以使堆栈比进入main
的堆栈更加对齐。我可以说服它跳过-m32 -mpreferred-stack-boundary=2
的堆栈对齐,告诉它不需要额外的对齐,用main:
pushl $0
call exit
编译你的测试程序给我
%ecx
与GCC 7.3。
13695 /* Grab the argument pointer. */
13696 t = plus_constant (Pmode, stack_pointer_rtx, m->fs.sp_offset);
13697 insn = emit_insn (gen_rtx_SET (crtl->drap_reg, t));
13698 RTX_FRAME_RELATED_P (insn) = 1;
13699 m->fs.cfa_reg = crtl->drap_reg;
13700 m->fs.cfa_offset = 0;
13701
13702 /* Align the stack. */
13703 insn = emit_insn (ix86_gen_andsp (stack_pointer_rtx,
13704 stack_pointer_rtx,
13705 GEN_INT (-align_bytes)));
13706 RTX_FRAME_RELATED_P (insn) = 1;
13707
的只写操作似乎是错过优化错误。它们来自ix86_expand_prologue
的这一部分:
main
目的是在重新排列堆栈之前保存指向传入参数区域的指针,以便可以直接访问参数。要么是因为这在管道中发生得相当晚(在寄存器分配之后),要么因为指令被标记为FRAME_RELATED,所以当它们变得不必要时,没有任何东西能够再次删除这些指令。
我想GCC开发人员至少会听一个关于此的错误报告,但是他们可能会合理地认为它是低优先级的,因为这些是在整个生命周期中只执行一次的指令程序,当TicketsController
不使用它的参数时,它们实际上只是死了,它们只发生在传统的32位ABI中,我现在认为它被认为是二等目标。
答案 1 :(得分:0)
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
上面的部分复制了调用堆栈帧,因为你没有定义main()的任何参数只包含返回地址-4(%ecx)和帧指针,进入$ 16字节对齐的堆栈;因此,我的WAG是为了适应不能正确对齐堆栈的运行时(crt0.s)。
push%ebp有点赠品 - 尽管有这个蹦床,但它通过crt0.s建立了一致的回溯。
这只是退出的“正常”调用,堆栈正确对齐......
subl $12, %esp
pushl $0
call exit