fastcall
http://coolengineer.com/133
원문은 제 블로그입니다만, 여기에도 포스팅합니다.
호출규약으로 번역되는 calling convention이라는 주제가 있다.
UNIX 쪽 C를 하는 사람들에게는 그다지 많이 다가오지 않는 주제일지 모르나, Windows 에서 프로그래밍을 하다보면, WINAPI라는 매크로를 사용할 때와 사용하지 않을 때가 있는 것을 볼 수 있는데, 저것은 실상 __stdcall 이라는 방식으로 선언하라는 것을 의미한다.
여기에는 중요한 두가지 요소가 있는데,
1. 인자 전달방식
2. 스택 청소 담당자
이다. 이런 차이에 의해 주위에서 많이 볼 수 있는 것이 다음 세가지이다.
1. cdecl
2. stdcall
3. fastcall
추가적으로 C++가 도입되면서 thiscall이라는 방식이 생겼지만, 이는 기본적으로 cdecl을 근간으로 하고 있으므로 생략하겠다. 또한 고생대의 pascal이나 far 등도 생략하겠다. 더불어 naked 로 선언되는 것도 생략한다.
cdecl과 stdcall은 다 안다고 가정하고... 흐흐흐...
요약정리하면, 스택을 비우는 책임이 cdecl은 호출자가 stdcall은 호출당한 놈이 있는데 그 차이가 있다. 이로 인해 가변인자를 처리하느냐(cdecl) 못하느냐(stdcall), 스택청소하는 코드가 곳곳에 산재하여 오브젝트 크기가 커지느냐(cdecl), 그렇지 않느냐(stdcall)의 차이로 구분할 수 있겠다.
요약한답시고 정리했지만 저게 다이므로 넘어가자. 내가 말하고 싶은 것은 fastcall이라는 것이다.
이 fastcall이라는 것은 내 기억상 borland c compiler에서 처음 도입되었다. 아니라면 지적해주시라.
아니, 도대체 뭐가 빠르단말이냐.
실상은 이렇다. 먼저 MSDN 문서를 살펴보면,
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_core___fastcall.asp
Argument-passing order:
The first two DWORD or smaller arguments are passed in ECX and EDX registers; all other arguments are passed right to left.
아니, 이것은 스택을 이용하지 않고 레지스터를 이용하여 인자를 넘기겠다는 발상아니냐! 그렇다면, 메모리에 들어갔다 나갔다를 하지 않을 것이고, 게다가 스택을 청소하는 일도 없을 것 아닌가!
단, 두개까지만 허용한댄다.
재밌는 발상이다. 저 문서에서 더 발견할 수 있는 것은 funcion naming decoration이 추가적으로 일어난다는 것을 알 수 있다.
즉, dumpbin.exe 명령으로 확인할 수 있는 function의 심볼들 앞에 @로 시작하는 놈들은 모두 fastcall 이라는 것이다.
그렇다면, linux의 gnu c compiler에서는 어떠한가.
http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gcc/function-attributes.html
에서 살펴보면, __attribute__안에 fastcall을 넣을 수가 있덴다.
그래서 사용해보니..
warning: `fastcall' attribute directive ignored
라는 좌절스러운 말이 전해온다. 그러나! 좀 위쪽에 regparm이라는 것이 있다..하하..
void __attribute__((regparm(3))) fastfunction( int x, int y, int z )
{
}
이와 같은 방식으로 사용하면 된단다. regparm(N)으로 인자를 N으로 주면 몇개는 인자를 레지스터로 넘기는 것이다.
저 문서에서 살펴보면, EAX, EDX, ECX가 사용된덴다.
심심하자나... 디스어셈블한번 해보자.
$ more a.c #includevoid __attribute__((regparm(3))) fastfunction( int x, int y, int z ) { printf("%d %d %d\n", x, y, z); } int main() { int x = 7; int y = 8; int z = 9; fastfunction( x, y, z ); return 0; }
야... 이거 gcc -O2 안해주면, fastcall 효과가 안난다.
$ gcc a.c -O2 -S $ vi a.s 1 .file "a.c" 2 .section .rodata.str1.1,"aMS",@progbits,1 3 .LC0: 4 .string "%d %d %d\n" 5 .text 6 .p2align 4,,15 7 .globl fastfunction 8 .type fastfunction, @function 9 fastfunction: 10 pushl %ebp 11 movl %esp, %ebp 12 subl $24, %esp 13 movl %eax, 8(%esp) 14 movl $.LC0, %eax 15 movl %eax, 4(%esp) 16 movl stdout, %eax 17 movl %ecx, 16(%esp) 18 movl %edx, 12(%esp) 19 movl %eax, (%esp) 20 call fprintf 21 movl %ebp, %esp 22 popl %ebp 23 ret 24 .size fastfunction, .-fastfunction 25 .p2align 4,,15 26 .globl main 27 .type main, @function 28 main: 29 pushl %ebp 30 movl $7, %eax 31 movl %esp, %ebp 32 subl $8, %esp 33 movl $9, %ecx 34 andl $-16, %esp 35 movl $8, %edx 36 call fastfunction 37 movl %ebp, %esp 38 xorl %eax, %eax 39 popl %ebp 40 ret 41 .size main, .-main 42 .ident "GCC: (GNU) 3.3.2"
대체 __attribute__((regparm(3))) 를 안쓰면 어떻게 되지?
1 .file "b.c" 2 .section .rodata 3 .LC0: 4 .string "%d %d %d\n" 5 .text 6 .globl fastfunction 7 .type fastfunction, @function 8 fastfunction: 9 pushl %ebp 10 movl %esp, %ebp 11 subl $24, %esp 12 movl 16(%ebp), %eax 13 movl %eax, 12(%esp) 14 movl 12(%ebp), %eax 15 movl %eax, 8(%esp) 16 movl 8(%ebp), %eax 17 movl %eax, 4(%esp) 18 movl $.LC0, (%esp) 19 call printf 20 leave 21 ret 22 .size fastfunction, .-fastfunction 23 .globl main 24 .type main, @function 25 main: 26 pushl %ebp 27 movl %esp, %ebp 28 subl $24, %esp 29 andl $-16, %esp 30 movl $0, %eax 31 subl %eax, %esp 32 movl $7, -4(%ebp) 33 movl $8, -8(%ebp) 34 movl $9, -12(%ebp) 35 movl -12(%ebp), %eax 36 movl %eax, 8(%esp) 37 movl -8(%ebp), %eax 38 movl %eax, 4(%esp) 39 movl -4(%ebp), %eax 40 movl %eax, (%esp) 41 call fastfunction 42 movl $0, %eax 43 leave 44 ret 45 .size main, .-main 46 .ident "GCC: (GNU) 3.3.2"
a.c의 fastfunction 내에서 printf에 대한 확장이 일어났었음을 감안해서 해석해야하는데, 위와 다른 것은 16(%ebp), 12(%ebp), 8(%ebp)로 접근하여 값을 가져온다는 것이다.
어, 이상하다? naming decoration (mangling)이 전혀 일어나지 않는다. 오브젝트만가지고 이것이 fastcall인지 알 수 있는 방법이 전혀없다.
MS의 오브젝트들은 dumpbin.exe의 심볼 덤프만으로 알 수 있는데 말이지.
그렇다면..? 장난한번 해볼까? 첫번째 인자가 eax로 넘어간다고 했으므로.. d.c 라는 프로그램을 다음과 같이 만들고
$ more d.c #includevoid __attribute__((regparm(1))) fastfunction( int x ) { printf("%d\n", x ); }
그리고 c.c라는 파일을 다음과 같이 만들되, 그 안의 fastfunction은 일반함수처럼 생기도록 선언하여 위 함수와 링크되게 한다.
$ more c.c #includevoid fastfunction( int x ); int main() { printf("hi?\n"); fastfunction( 0 ); printf("hello?\n"); fastfunction( 0 ); printf("I am Hojin?\n"); fastfunction( 0 ); printf("Welcome to real world\n"); fastfunction( 0 ); printf("Help me Gandalf!\n"); fastfunction( 0 ); return 0; }
원래 함수의 8 byte 이내의 크기를 가진 리턴값은 %eax에 저장되므로, printf의 return 값인 출력한 크기가 %eax에 남아 있게 되고 이것은 그대로 fastfunction에서 첫번째 인자로 동작할 것이다.
자, 위를 실행해보자..
$ ./a.out hi? 4 hello? 7 I am Hojin? 12 Welcome to real world 22 Help me Gandalf! 17
음.. 됐어. 됐어...
재밌고 유익하게 잘
재밌고 유익하게 잘 봤습니다. :)
그런데, 개인 blog에 적고 또 copy라니 뭔가 좀 어색하다는 느낌입니다.
원문은 다른 blog에 있고, 그걸 마치 이곳에 post한것처럼 link거는 방법은 없는걸까요?
\(´∇`)ノ \(´∇`)ノ \(´∇`)ノ \(´∇`)ノ
def ed():neTdiVeR in range(thEeArTh)
--------------------------------------------------------------------------------
\(´∇`)ノ \(´∇`)ノ \(´∇`)ノ \(´∇`)ノ
def ed():neTdiVeR in range(thEeArTh)
실험중입니다.
저도 어색합니다. :lol:
Drupal이 그러한 피딩기능은 제공하지 않기 때문에 어쩔 수 없는 것이죠.
RSS를 조합하여 피딩하는 다른 사이트처럼 가능하다면 괜찮겠습니다만,
다른사람이 퍼 나른것도 아니고, 저자인 제가 옮긴 것이므로 일반 펌질의 의미와는 조금 다를 것 같습니다.
저 말고도 많은 블로거들이 저와 비슷한 문제로 고민할 것도 같은데요...
일단은 실험모드로 해보려고합니다.
---
http://coolengineer.com
아~ 어려워...
아~ 어려워... 어려워... :sick:
그래도 모처럼 재미있는 글이었습니다. :-)
----------------------
좋은 하루되세요.
------------------------------
좋은 하루 되세요.
오~ 간만에 C++ 구경이군요~
개인 사정으로 C++ 프로그래밍할 일이 줄어 들면서 이런 깊이 있는 예기들과 멀어졌었는데....
간만에 이런 포스트를 보니까 괜히 기분이 좋아지네요 ^^;
좋은 글 고맙습니다~
재밌군요.
물마시듯 순순히 이해가가는건 아니지만,
어느정도 흥미가 있던관계로 재미있게 읽었습니다.
:)
---------------------
한글을 사랑합니다.
-----------------
한글을 사랑합니다.
댓글 달기