Pour approfondir la réponse de PolyThinker, voici un exemple concret.
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
sortie :
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
sortie :
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
Dans le premier cas, <foo+0x11>-<foo+0x1d>
pousse les arguments pour un appel de fonction, alors que dans le second cas, <foo+0x11>-<foo+0x14>
modifie les variables et jmp
à la même fonction, quelque part après le préambule. C'est ce que vous devez chercher.
Je ne pense pas que l'on puisse faire cela de manière programmatique ; il y a trop de variations possibles. La "chair" de la fonction peut être plus proche ou plus éloignée du début, et vous ne pouvez pas le distinguer jmp
d'une boucle ou d'une conditionnelle sans la regarder. Il pourrait s'agir d'un saut conditionnel au lieu d'une jmp
. gcc
pourrait laisser un call
dans certains cas, mais appliquer l'optimisation des appels entre frères et sœurs dans d'autres cas.
Pour votre information, les "appels fratries" de gcc sont légèrement plus généraux que les appels récursifs de queue -- en fait, tout appel de fonction où la réutilisation du même cadre de pile est acceptable est potentiellement un appel fratrie.
[modifier]
A titre d'exemple, lorsque l'on cherche simplement une auto-récursive call
vous induira en erreur,
int bar(int n) {
if (n == 0)
return bar(bar(1));
if (n % 2)
return n;
return bar(n / 2);
}
GCC appliquera l'optimisation de l'appel de la fratrie à deux sur les trois bar
appels. Je dirais encore qu'il est optimisé pour les appels de queue, puisque cet appel unique non optimisé ne va jamais plus loin qu'un seul niveau, même si vous trouverez un fichier call <bar+..>
dans l'assemblage généré.