扩展 PolyThinker 的答案,这里有一个具体的例子。
int foo(int a, int b) {
if (a && b)
return foo(a - 1, b - 1);
return a + b;
}
i686-pc-linux-gnu-gcc-4.3.2 -Os -fno-optimize-sibling-calls
输出:
00000000 <foo>:
0: 55 推 %ebp
1: 89 e5 移动 %esp,%ebp
3: 8b 55 08 移动 0x8(%ebp),%edx
6: 8b 45 0c 移动 0xc(%ebp),%eax
9: 85 d2 测试 %edx,%edx
b: 74 16 je 23 <foo+0x23>
d: 85 c0 测试 %eax,%eax
f: 74 12 je 23 <foo+0x23>
11:51 推送 %ecx
12:12 月 48 日
13: 51 推送 %ecx
14: 50 推%eax
15: 8d 42 ff lea -0x1(%edx),%eax
18: 50 推%eax
19: e8 fc ff ff ff 调用 1a <foo+0x1a>
1e: 83 c4 10 添加 $0x10,%esp
21: eb 02 jmp 25 <foo+0x25>
23: 01 d0 添加 %edx,%eax
25:c9离开
26:c3 回复
i686-pc-linux-gnu-gcc-4.3.2 -Os
输出:
00000000 <foo>:
0: 55 推 %ebp
1: 89 e5 移动 %esp,%ebp
3: 8b 55 08 移动 0x8(%ebp),%edx
6: 8b 45 0c 移动 0xc(%ebp),%eax
9: 85 d2 测试 %edx,%edx
b: 74 08 je 15 <foo+0x15>
d: 85 c0 测试 %eax,%eax
f: 74 04 je 15 <foo+0x15>
11:12 月 48 日
12: 12 月 4 日 %edx
13: eb f4 jmp 9 <foo+0x9>
15: 5d 弹出 %ebp
16: 01 d0 添加 %edx,%eax
18:c3 雷特
在第一种情况下,<foo+0x11>-<foo+0x1d>
为函数调用推送参数,而在第二种情况下,<foo+0x11>-<foo+0x14>
将变量和jmp
s 修改为同一个函数,位于序言之后的某个位置。这就是你想要寻找的。
我认为您不能以编程方式执行此操作;有太多可能的变化。函数的“肉”可能离开始更近或更远,如果jmp
不看它,你就无法将其与循环或条件区分开来。它可能是条件跳转而不是jmp
. gcc
在某些情况下可能会保留一个call
in,但对其他情况应用同级调用优化。
仅供参考,gcc 的“兄弟调用”比尾递归调用更通用——实际上,任何可以重用相同堆栈帧的函数调用都可能是兄弟调用。
[编辑]
例如,当仅寻找自递归call
会误导您时,
int bar(int n) {
if (n == 0)
return bar(bar(1));
if (n % 2)
return n;
return bar(n / 2);
}
GCC 将对三个bar
调用中的两个应用同级调用优化。我仍然称它为尾调用优化,因为单个未优化的调用永远不会超过单个级别,即使您会call <bar+..>
在生成的程序集中找到 a 。