_cdecl调用约定

时间:2012-02-28 08:03:02

标签: assembly x86 stackframe cdecl

在一篇关于_cdecl调用约定的文章中,作者提到:

  

释放本地存储空间       当函数分配本地临时空间时,它通过从堆栈点减少所需的空间量来实现这一点   必须颠倒过程才能收回那个空间。它通常是由   向堆栈指针添加相同的减去量   以前,虽然一系列POP指令可以达到同样的目的   的事情。

我的问题是:我可以简单地将ESP设置为当前的EBP值而不是“将相加的数量添加到堆栈指针”或“一系列POP指令”吗?

喜欢:

mov esp, ebp

对我来说似乎是一种更好的方式,因为如果我以后更改了这个函数的局部变量的数量,我就不必费心去增加值了。

2 个答案:

答案 0 :(得分:4)

从技术上讲,这将是滥用堆栈帧,这是为了捕获堆栈不平衡引起的错误,但它是完全合法的。

但有一些事项需要注意,如果你有非常有限的堆栈空间,由于大的alloc或嵌入式设备,每次调用后清理堆栈都是值得的。它还使调试更容易,因为你知道有些东西使用了错误的args。

如果有人要维护你的代码,他们会发现它很混乱。

答案 1 :(得分:2)

实际上,这正是leave instruction所做的,它是为了支持高级语言而引入的。但是,它并没有经常使用;大多数编译器只做明确的mov esp, ebp; pop ebp序列。另请参阅this question

但是,有时您可以执行“省略帧指针”优化。这释放了EBP以用作通用寄存器,但是您(或编译器)必须在整个函数期间跟踪ESP更改并使用可能更改的偏移量来寻址局部变量或传入参数。如果你这样做,你必须使用pops或显式添加将ESP恢复为原始值,然后再返回。

注意上述内容涉及整个功能(即prolog / epilog);当您需要在函数中间调用特定的__cdecl函数时,不能只将ESP恢复为EBP值,因为该值仅在函数的最开头有效,在为局部变量分配任何空间之前。这里有两种方法:

1)推送参数,在通话后恢复ESP:

push offset msg
call _printf
pop ecx ; clobbers ECX but shorter than add esp, 4

2)将参数移到保留的堆栈槽位;在这种情况下你不需要恢复ESP:

mov dword ptr [esp+0], offset msg
call _printf
; no need to change ESP

如果选择第二个选项,则需要确保不在这些插槽中存储任何局部变量。此外,此类mov指令通常比推送时间长,因此如果代码大小是一个问题,您可能需要考虑。