#include[Listing 2] 인라인이 적용된 소스inline test(int a, int b, int c) { int d; d=a*b*c; printf("%d * %d * %d is %dn",a,b,c,d); } static inline test2(int a, int b, int c) { int d; d=a+b+c; printf("%d + %d + %d is %dn",a,b,c,d); } int main(int argc, char *argv[]) { test(1,2,3); test2(4,5,6); }
$ gcc -S -O3 -o-S는 gcc를 컴파일 단계 후에 바로 멈추도록 합니다. (우리는 이 부분을 나중에 다룰 것입니다.) 결과는 다음과 같습니다.
.... test: pushl %ebp movl %esp, %ebp pushl %ebx .... main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) ... movl $6, 16(%esp) movl $3, 12(%esp) movl $2, 8(%esp) movl $1, 4(%esp) movl $.LC0, (%esp) call printf ... movl $15, 16(%esp) movl $6, 12(%esp) movl $5, 8(%esp) movl $4, 4(%esp) movl $.LC1, (%esp) call printf ...test() 와 test2() 양쪽 모두 인라인을 추가하지만, main() 바깥쪽에 남아있는 test()를 볼 것입니다. 이것은 규칙상 정적인 키워드(static keyword)가 위치하는 곳입니다. 함수를 static 이라고 말하는 것으로 여러분은 gcc가 이 함수를 어떤 다른 오브젝트 파일에 의해 호출할 것이라고 말한 것입니다. 따라서 이 코드를 일부러 생략할 필요가 없어졌습니다. 게다가 여러분이 이 함수를 언제나 사용 가능하도록 static으로 표시할 수 있다면 이 방법은 공간을 줄여줍니다. 반면에 어떤 함수를 인라인 적용할 것인지 결정할 때에는 현명해야 합니다. 작은 속도 성능을 위해 크기를 늘리는 것이 항상 가치가 있는 것은 아닙니다.
__attribute__((always_inline)) static inline test(int a, int b, int c)이제 파라메터 전달을 합니다. x86 구조에서 파라메터들은 스택에 저장(push)되었다가 더 나은 처리를 하기 위해 함수 안에서 불러옵니다(pop). 그러나 gcc는 여러분들에게 이러한 동작을 레지스터로 대신 사용하도록 기회를 제공합니다. 3개 이상의 파라메터를 갖는 함수들은 이러한 요소들을 -mregparm=
... test: pushl %ebp movl %esp, %ebp subl $56, %esp movl %eax, -20(%ebp) movl %edx, -24(%ebp) movl %ecx, -28(%ebp) ... main: ... movl $3, %ecx movl $2, %edx movl $1, %eax call test스택 대신에 이것은 처음, 두 번째, 세 번째 파라메터를 기억해 두기 위해, EAX, EDX, 그리고 ECX를 사용합니다. 레지스터 접속 시간이 RAM보다 빠르기 때문이 이것은 실행시간을 줄이는 한가지 방법이 됩니다. 하지만 여러분들은 이 사항들을 주의하십시오.
push %ebp mov %esp,%ebp sub $0x28,%esp이 작업 순서는 함수 프롤로그라고 알려져 있으며, 프레임 포인터(EBP)를 설정하기 위해 쓰여집니다. 이것으로 디버거가 스택 트레이스를 하는데 도움을 줍니다. 아래의 구조는 여러분들이 이것을 시각적으로 보는데 도움이 될 것입니다. [6]
[ebp-01] 마지막 지역 변수의 마지막 바이트 [ebp+00] 오래된 ebp 값 [ebp+04] 주소를 되돌려주기 [ebp+08] 첫 번째 인자이것을 생략할 수 있을까요? 네, -fomit-frame-pointer 로, 함수 프롤로그를 단축하여 함수가 (지역변수가 있을 경우에만) 스택을 예약하는 것만으로 시작할 수 있습니다.
sub $0x28,%esp만약 함수가 자주 빈번하게 호출된다면, 프롤로그를 잘라내는 것으로 여러분의 프로그램에서 몇몇 CPU 동작을 줄입니다. 그러나 주의하십시오. 프롤로그를 잘라내는 것으로 여러분은 디버거가 스택 분석을 사용하기 힘들게 만들 것입니다. 예를 들어, test2 끝에 test(7,7,7)을 추가해보시고, -fomit-frame-pointer와 함께 최적화 파라메터 없이 다시 컴파일 해보십시오. 이제 gdb를 실행해서 바이너리 파일을 들여보십시오.
$ gdb inline (gdb) break test (gdb) r Breakpoint 1, 0x08048384 in test () (gdb) cont Breakpoint 1, 0x08048384 in test () (gdb) bt #0 0x08048384 in test () #1 0x08048424 in test2 () #2 0x00000007 in ?? () #3 0x00000007 in ?? () #4 0x00000007 in ?? () #5 0x00000006 in ?? () #6 0x0000000f in ?? () #7 0x00000000 in ?? ()두 번째 test 호출에서 프로그램은 멈추고 gdb는 스택 트레이스를 출력합니다. 보통 main()은 프레임 #2에 나타날 것입니다만, 우리는 물음표만 보게됩니다. 제가 스택 레이아웃에 관하여 언급했던 소스를 다시 호출하여 보십시오. 프레임 포인터의 부재가 gdb를 프레임 #2에 저장된 반환 주소의 위치를 찾지 못하도록 방해합니다.
이전 글 : GCC 파라메터와 친해지기(1)
다음 글 : GCC 파라메터와 친해지기(3)
최신 콘텐츠