2010-12-26 2 views
17

에주의 : 그 이후왜 밀어 넣기 대신 movl을 사용합니까? 이 코드에

#include <stdio.h> 
void a(int a, int b, int c) 
{ 
    char buffer1[5]; 
    char buffer2[10]; 
} 

int main() 
{ 
    a(1,2,3); 
} 

:

gcc -S a.c 

명령 어셈블리 우리의 소스 코드를 보여줍니다.

이제 우리는 주 함수에서 "밀어 넣기"명령을 사용하여 인수를 스택에 밀어 넣지 않을 수 있습니다. 그 대신에 "movel"을 사용했습니다.

main: 
pushl %ebp 
movl %esp, %ebp 
andl $-16, %esp 
subl $16, %esp 
movl $3, 8(%esp) 
movl $2, 4(%esp) 
movl $1, (%esp) 
call a 
leave 

왜 그런가요? 그들 사이의 차이점은 무엇입니까?

답변

16

는 GCC 수동 그것에 대해 말을하는 것입니다 :

-mpush-args 
-mno-push-args 
    Use PUSH operations to store outgoing parameters. This method is shorter and usually 
    equally fast as method using SUB/MOV operations and is enabled by default. 
    In some cases disabling it may improve performance because of improved scheduling 
    and reduced dependencies. 

-maccumulate-outgoing-args 
    If enabled, the maximum amount of space required for outgoing arguments will be 
    computed in the function prologue. This is faster on most modern CPUs because of 
    reduced dependencies, improved scheduling and reduced stack usage when preferred 
    stack boundary is not equal to 2. The drawback is a notable increase in code size. 
    This switch implies -mno-push-args. 

분명히 -maccumulate-outgoing-args은 기본적으로 활성화되어, -mpush-args를 오버라이드 (override)

내가 기사를 발견했다. -mno-accumulate-outgoing-args으로 명시 적으로 컴파일하면 PUSH 메소드로 되돌아갑니다.

+4

이 '부풀림 생성 옵션'인'-maccumulate-outgoing-args'가'-Os'에 의해 자동으로 비활성화되지 않는 이유는 훨씬 더 나은 질문 일 것입니다. –

+0

@R .. 왜 그런지 알아? – Tony

+0

@ 토니 : 분명히, 각각의 특정 -O 옵션에 대해 많은 (~ 200) 개의 최적화 플래그를 활성화/비활성화하도록 결정할 때 상황이 균열을 통과하는 경우가 있기 때문입니다. – ninjalj

8

그 코드는 상수 (1, 2, 3)를 (업데이트 된) 스택 포인터 (esp)의 오프셋 위치에 직접 놓는 것입니다. 컴파일러는 동일한 결과로 수동으로 "푸시 (push)"를 선택합니다.

"push"는 데이터를 설정하고 스택 포인터를 업데이트합니다. 이 경우 컴파일러는 스택 포인터를 한 번만 업데이트하면됩니다 (세 개). 흥미로운 실험은 "a"함수를 변경하여 하나의 인수 만 취하고 명령어 패턴이 변경되는지 확인하는 것입니다.

+0

먼저 레지스터에 상수를 넣어야하는 이유는 무엇입니까? x86은 직접 상수의 푸시를 지원합니다. – Necrolis

+0

@Necrolis : 충분히 공정합니다. 편집 됨. 고마워. –

0

펜티엄 명령어 세트에는 상수를 스택에 푸시하기위한 명령어가 없습니다. 그래서 느린 것 push를 사용하여 :

... 
movl $1, %eax 
pushl %eax 
... 

그래서 컴파일러가 movl를 사용하여 빠르게 감지 : 프로그램은 레지스터에 상수를 넣어 레지스터를 밀어해야합니다. 난 당신이 대신 일정의 변수와 함수를 호출 시도 할 수 있습니다 같아요

int x; 
scanf("%d", &x); // make sure x is not a constant 
a(x, x, x); 
+6

상수를 푸시하는 작업이 80286부터 지원되었습니다. 아마도 gcc가 기본값 8086 코드를 생성하고 있습니까? –

+1

x86 명령어 세트에 대한 제 지식이 다소 오래되었습니다 (20 년) :-) – anatolyg

6

GCC는에 최적화되는 특정 CPU의 실행 속도에 따라 지침을 선택 포함한 최적화의 모든 종류를 않습니다. x *= n과 같은 것들은 종종 n이 상수 일 때 SHL, ADD 및/또는 SUB의 혼합으로 대체됩니다. MUL은 SHL-ADD-SUB의 조합의 평균 런타임 (및 캐시/etc. footprints)이 MUL의 조합을 초과하거나 n이 상수가 아닌 경우에만 사용됩니다 (따라서 shl-add-sub와 루프를 사용하면 costlier 올).

함수 인수의 경우 : MOV는 하드웨어로 병렬화 할 수 있지만 PUSH는 병렬화 할 수 없습니다. (두 번째 PUSH는 esp 레지스터의 업데이트로 인해 첫 번째 PUSH가 완료 될 때까지 기다려야합니다.) 함수 인수의 경우 MOV를 병렬로 실행할 수 있습니다.

+0

이러한 종류의 최적화에 대한 참고 자료가 있습니까? 감사. – Tony

2

언제든지 OS X에 있나요? 나는 스택 포인터가 16 바이트 경계에서 정렬되어야한다고 어딘가에서 읽었다. 아마도 이런 종류의 코드 생성을 설명 할 수 있습니다. http://blogs.embarcadero.com/eboling/2009/05/20/5607 여기

+1

OS X ABI는 외부 함수 호출 지점에서 스택 포인터가 16 바이트로 정렬되어 있어야합니다. –

+0

그 점을 지적 해 주셔서 감사합니다. 다른 대답 읽기 이제는 movl 코드 생성이 향상된 스케줄링과 관련되어 있음을 이해합니다. andl 명령은 스택 정렬을 위해서만 존재하는 것처럼 보입니다. –

관련 문제