nic*_*pro 5 c linux x86 gcc abi
当前 (Linux) 版本的 SysV i386 ABI 需要在调用之前进行 16 字节堆栈对齐:
输入参数区域的末尾应在 16(32,如果 __m256 在堆栈上传递)字节边界上对齐。换句话说,当控制权转移到函数入口点时,值 (%esp + 4) 始终是 16 (32) 的倍数。
在 GCC 8.1 上,此代码在调用之前将堆栈与 16 字节边界对齐callee:( Godbolt )
| 来源 | # 字节 | 
|---|---|
| 称呼 | 4 | 
| 推送ebp | 4 | 
| 子特别是,24 | 24 | 
| 子特别, 4 | 4 | 
| 推入eax | 4 | 
| 推入eax | 4 | 
| 推入eax | 4 | 
| 全部的 | 48 | 
在 GCC 8.2 及更高版本的所有版本上,它与 4 字节边界对齐:( Godbolt )
| 来源 | # 字节 | 
|---|---|
| 称呼 | 4 | 
| 推送ebp | 4 | 
| 子特别,16 | 16 | 
| 推入eax | 4 | 
| 推入eax | 4 | 
| 推入eax | 4 | 
| 全部的 | 36 | 
如果我们缩短或增加所需的参数数量,则很容易验证callee。
奇怪的是,更改-mprefered-stack-boundary会将操作数更改为子指令,但不会更改实际的堆栈对齐方式:(Godbolt)
那么,呃,什么给出了?
由于您在同一翻译单元中提供了函数的定义,显然 GCC 认为该函数不关心堆栈对齐,也不太关心它。显然,即使在-O0.
当我在手册中搜索“ipa”选项时,这个选项甚至有一个明显的名称:-fipa-stack-alignment即使在-O0. 手动将其关闭,-fno-ipa-stack-alignment结果如您所料,一秒sub的值取决于推送次数 ( Godbolt ),确保 ESP 在调用之前按 16 对齐,就像现代 Linux 版本的 i386 SysV ABI 使用一样。
或者,如果您将定义更改为只是一个声明,那么生成的 asm 就如预期的那样,完全尊重-mpreferred-stack-boundary.
void callee(void* a, void* b) {
}
到
void callee(void* a, void* b);
使用-fPIC还强制 GCC 不假设任何有关被调用者的信息,因此它确实尊重使用适当选项进行函数插入的可能性(例如通过 LD_PRELOAD)。
在不编译共享库的情况下,GCC 可以假设它看到的全局函数的任何定义都是定义,这要归功于 ISO C 的单一定义规则。
如果您__attribute__((noipa))在函数定义上使用,则调用站点不会根据定义进行任何假设。就像您重命名了定义(这样您仍然可以查看它)并仅提供调用者使用的名称的声明一样。
如果您只想停止内联,则可以改为使用__attribute__((noinline,noclone)),仍然允许调用站点就像优化器只是选择不内联一样,但仍然可以看到此定义。这可能是也可能不是您想要的。
另请参阅如何从 GCC/clang 程序集输出中删除“噪音”?回复:编写其 asm 看起来很有趣的函数以及编译器选项。
顺便说一句,我发现将声明/定义更改为可变参数最简单,因此我只需更改调用者即可添加或删除参数。我仍然能够重现您的结果,即使金额随着额外的参数而变化,当有定义但不仅仅是声明时,金额也不会改变。subpush
void callee(void* a, ...)  // {}   // comment out a body or not
;