Расширяя ответ 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 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 55 08 mov 0x8(%ebp),%edx
6: 8b 45 0c mov 0xc(%ebp),%eax
9: 85 d2 test %edx,%edx
b: 74 16 je 23 <foo+0x23>
d: 85 c0 test %eax,%eax
f: 74 12 je 23 <foo+0x23>
11: 51 push %ecx
12: 48 dec %eax
13: 51 push %ecx
14: 50 push %eax
15: 8d 42 ff lea -0x1(%edx),%eax
18: 50 push %eax
19: e8 fc ff ff ff call 1a <foo+0x1a>
1e: 83 c4 10 add $0x10,%esp
21: eb 02 jmp 25 <foo+0x25>
23: 01 d0 add %edx,%eax
25: c9 leave
26: c3 ret
i686-pc-linux-gnu-gcc-4.3.2 -Os
выход:
00000000 <foo>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 55 08 mov 0x8(%ebp),%edx
6: 8b 45 0c mov 0xc(%ebp),%eax
9: 85 d2 test %edx,%edx
b: 74 08 je 15 <foo+0x15>
d: 85 c0 test %eax,%eax
f: 74 04 je 15 <foo+0x15>
11: 48 dec %eax
12: 4a dec %edx
13: eb f4 jmp 9 <foo+0x9>
15: 5d pop %ebp
16: 01 d0 add %edx,%eax
18: c3 ret
В первом случае <foo+0x11>-<foo+0x1d>
выдвигает аргументы для вызова функции, а во втором случае <foo+0x11>-<foo+0x14>
изменяет переменные и jmp
s для той же функции, где-то после преамбулы. Это то, что вы хотите искать.
Я не думаю, что вы можете сделать это программно; слишком много возможных вариаций «Мясо» функции может быть ближе или дальше от начала, и вы не сможете отличить это jmp
от цикла или условия, не глядя на него. Это может быть условный переход вместо jmp
. gcc
может оставить call
в некоторых случаях, но применить оптимизацию вызова родного брата в других случаях.
К вашему сведению, "родственные вызовы" gcc немного более общие, чем хвостовые рекурсивные вызовы - фактически любой вызов функции, в котором повторное использование одного и того же стекового фрейма является нормальным, потенциально является родственным вызовом.
[править]
В качестве примера, когда просто поиск саморекурсора call
введет вас в заблуждение,
int bar(int n) {
if (n == 0)
return bar(bar(1));
if (n % 2)
return n;
return bar(n / 2);
}
GCC применяет оптимизацию вызовов родного брата к двум из трех вызовов bar
. Я бы по-прежнему назвал его оптимизированным с помощью tail-call, поскольку этот неоптимизированный вызов никогда не идет дальше одного уровня, даже если вы найдете call <bar+..>
в сгенерированной сборке.