Endrekursionen sind Rekursionen, bei denen der rekursive Funktionsaufruf die letzte Operation zum Berechnen des Funktionsergebnisses ist. Eine wichtige Eigenschaft von Endrekursionen ist, dass sie zu Iterationen transformiert werden können, d.h. der Speicherverbrauch auf dem Stack bleibt konstant, während bei nicht-endrekursiven Funktionen der Speicherverbrauch auf dem Stack mit der Rekursiontiefe wächst. Das ist insbesondere relevant für funktionale Sprachen, deren einziges Mittel zur Iterationen die Rekursion darstellt. Das sei einmal der Theorieteil.
Gestern hab ich mir aufgrund einer Diskussion mit
nion zu dem Thema mal angeschaut, was der gcc mit endrekursiven Funktionen in C denn genau macht. Und siehe da: der gcc transformiert endrekursive C-Funktionen doch tatsächlich zu Iterationen! Folgende Codesnippets zeigen es deutlich:
int sum (int n) {
if (n == 0)
return 0;
return n + sum (n - 1);
}
wird zu
sum:
pushl %ebp
xorl %eax, %eax
movl %esp, %ebp
movl 8(%ebp), %edx
.L3:
testl %edx, %edx
je .L4
addl %edx, %eax
decl %edx
jmp .L3
.L4:
popl %ebp
ret
Damit das auch etwas praxisrelevanter wird, hier der Code zum rekursiven Freigeben einer verketteten Liste:
void delete_word_r(word_t * e) {
if (e) {
word_t* next = e->next;
if (e->word)
free(e->word);
free(e);
delete_word_r(next);
}
}
Der gcc macht folgendes daraus:
delete_word_r:
pushl %ebp
movl %esp, %ebp
pushl %esi
pushl %ebx
movl 8(%ebp), %ebx
.L33:
testl %ebx, %ebx
je .L38
movl (%ebx), %eax
movl 4(%ebx), %esi
testl %eax, %eax
je .L36
subl $12, %esp
pushl %eax
call free
addl $16, %esp
.L36:
subl $12, %esp
pushl %ebx
movl %esi, %ebx
call free
addl $16, %esp
jmp .L33
.L38:
leal -8(%ebp), %esp
popl %ebx
popl %esi
popl %ebp
ret
Coole Sache. In beiden Assembler-Snippets ist weder ein "call sum" noch ein "call delete_word_r" zu finden.