2012-12-30 10 views
7

나는 리눅스에 gcc가 4.4.5과 GCC-LLVM 맥 OSX에 (엑스 코드 4.2.1) 및 this에 다음 코드를 시도했습니다. 아래에는 관련 기능의 소스와 생성 된 디스 어셈블리가 나와 있습니다. 인수 옆에 연산자를 (추가 gcc -O2 main.c 컴파일)왜이 코드가 꼬리 호출 최적화에서 gcc & llvm을 막지 않습니까?

#include <stdio.h> 
__attribute__((noinline)) 
static void g(long num) 
{ 
     long m, n; 
     printf("%p %ld\n", &m, n); 
     return g(num-1); 
} 
__attribute__((noinline)) 
static void h(long num) 
{ 
     long m, n; 
     printf("%ld %ld\n", m, n); 
     return h(num-1); 
} 
__attribute__((noinline)) 
static void f(long * num) 
{ 
     scanf("%ld", num); 
     g(*num); 
     h(*num);   
     return f(num); 
} 
int main(void) 
{ 
     printf("int:%lu long:%lu unsigned:%lu\n", sizeof(int), sizeof(long), sizeof(unsigned)); 
     long num; 
     f(&num);     
     return 0; 
} 

08048430 <g>: 
8048430: 55     push %ebp 
8048431: 89 e5    mov %esp,%ebp 
8048433: 53     push %ebx 
8048434: 89 c3    mov %eax,%ebx 
8048436: 83 ec 24    sub $0x24,%esp 
8048439: 8d 45 f4    lea -0xc(%ebp),%eax 
804843c: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp) 
8048443: 00 
8048444: 89 44 24 04   mov %eax,0x4(%esp) 
8048448: c7 04 24 d0 85 04 08 movl $0x80485d0,(%esp) 
804844f: e8 f0 fe ff ff  call 8048344 <[email protected]> 
8048454: 8d 43 ff    lea -0x1(%ebx),%eax 
8048457: e8 d4 ff ff ff  call 8048430 <g> 
804845c: 83 c4 24    add $0x24,%esp 
804845f: 5b     pop %ebx 
8048460: 5d     pop %ebp 
8048461: c3     ret  
8048462: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi 
8048469: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi 

08048470 <h>: 
8048470: 55     push %ebp 
8048471: 89 e5    mov %esp,%ebp 
8048473: 83 ec 18    sub $0x18,%esp 
8048476: 66 90    xchg %ax,%ax 
8048478: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp) 
804847f: 00 
8048480: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 
8048487: 00 
8048488: c7 04 24 d8 85 04 08 movl $0x80485d8,(%esp) 
804848f: e8 b0 fe ff ff  call 8048344 <[email protected]> 
8048494: eb e2    jmp 8048478 <h+0x8> 
8048496: 8d 76 00    lea 0x0(%esi),%esi 
8048499: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi 

080484a0 <f>: 
80484a0: 55     push %ebp 
80484a1: 89 e5    mov %esp,%ebp 
80484a3: 53     push %ebx 
80484a4: 89 c3    mov %eax,%ebx 
80484a6: 83 ec 14    sub $0x14,%esp 
80484a9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi 
80484b0: 89 5c 24 04   mov %ebx,0x4(%esp) 
80484b4: c7 04 24 e1 85 04 08 movl $0x80485e1,(%esp) 
80484bb: e8 94 fe ff ff  call 8048354 <[email protected]> 
80484c0: 8b 03    mov (%ebx),%eax 
80484c2: e8 69 ff ff ff  call 8048430 <g> 
80484c7: 8b 03    mov (%ebx),%eax 
80484c9: e8 a2 ff ff ff  call 8048470 <h> 
80484ce: eb e0    jmp 80484b0 <f+0x10> 

우리는 g()h()& (주소)를 제외한 대부분 동일하다는 것을 알 수 printf()m (과 무관 %ld%p). 그러나, h()는 꼬리 호출 최적화하고 g()가 없습니다. 왜? g에서

답변

6

()는 로컬 변수의 주소를 복용하고 함수에 전달하고 있습니다. "충분히 똑똑한 컴파일러"는 printf가 그 포인터를 저장하지 않는다는 것을 알아야합니다. 대신 gcc와 llvm은 printf가 어딘가에 포인터를 저장할 수 있다고 가정하므로 m이 포함 된 호출 프레임이 재귀에서 "실행"되어야 할 수도 있습니다. 따라서 TCO가 없습니다.

3

그것은을 수행하는 &입니다. 스택에 m을 저장해야한다는 것을 컴파일러에 알려줍니다. 이 printf에 전달하더라도, 컴파일러는 다른 사람에 액세스 할 수 있으며, 따라서 g에 대한 호출 후 스택에서 청소해야한다고 생각한다.

printf이 컴파일러에 알려져 있으므로 (포인터를 저장하지 않는다는 것을 알고 있으므로)이 최적화를 수행하는 방법을 배울 수 있습니다.

이에 대한 추가 정보를 원하시면, '탈출 anlysis'를 찾아보십시오.