fastcall

  호출규약으로 번역되는 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
#include

void __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
#include

void __attribute__((regparm(1))) fastfunction( int x )
{
printf("%d\n", x );
}



그리고 c.c라는 파일을 다음과 같이 만들되, 그 안의 fastfunction은 일반함수처럼 생기도록 선언하여 위 함수와 링크되게 한다.


$ more c.c
#include

void 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


음.. 됐어. 됐어...

이 글은 스프링노트에서 작성되었습니다.

.

'Computer > C' 카테고리의 다른 글

소켓 프로그래밍  (0) 2012.08.10
소켓 플래쉬 xml  (0) 2012.08.10
디스어셈블을 통한 함수 호출 이해  (0) 2012.08.10
fastcall  (0) 2012.08.10
TAGS.

Comments 0