아래 주소의 글을 마음대로 내키는 대로 마구잡이로 번역 / 추가 / 삭제한 글입니다. (비난은 금물. 틀린부분 지적은 대환영!)
http://www.unixwiz.net/techtips/win32-callconv.html
----------------------------------------------------------
* Calling Conventions
전통적으로 C 함수의 호출은 호출부쪽에서 인자들은 Stack에 push한고 함수를 호출후에 push한 인자들을 정리한다.
/* __cdecl 예제 */
push arg1
push arg2
push arg3
call function
add sp,12 // pop, pop, pop 한것 같은 효과. (다쓴 인자 정리.)
(* sp -> stack pointer )
문론 MS 컴파일러는 이 방법만 지원하는것은 아니다. 이와 다른 두가지 방법을 더 지원한다. 자세한 내용은 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore98/html/_core_calling_conventions_topics.asp)를 참고.
또한 위에서 말한 __cdecl 컨벤션이 MS VC/.net 컴파일러는 default로 사용된다. (문론 바꿀수도 있다.)
이와 다르게 또다른 컨벤션인 __stdcall에 대하여 알아보자.
__stdcall은 위에서 말한 __cdecl과 같이 호출부에서 stack에 인자를 push한다. 그러나 __cdecl과 다르게 호출되는 함수내에서 stack을 정리한다. 이것이 Win32 API 함수의 standard convention 이다. (이것이 WINAPI 이며 windows.h에 선언되어 있다.) 그리고 때때로 "Pascal" 컬링 컨벤션이라고도 불린다.
/* __stdcall 예제 */
push arg1
push arg2
push arg3
call function
// 스택을 정리하는 부분이 없다. (호출되어지는 함수에 정리 부분이 있다.)
지금까지 말한것들이 별로 중요하지 않은 내용처럼 보이지만, 만약에 스택을 호출부와 호출당한곳에서 어떻게 다루어지는지에 대한 정리가 되어 있지 않다면 스택은 꺠어질것이고 북구할수 없을것이다.
"A mismatch in calling convention is catastrophic for a running program."
또한 우리는 __fastcall에 대하여도 기억하여야 한다. __fastcall은 레지스터를 이용한다. 그러나 일반적인 경우에 유용할것이라고 생각되지 않는다. 그러나 저장하고 가져오는데에 레지스터를 사용하면 속도면에서 유리하다.
. __stdcall이 스택을 정리할때에 작은(매우)코드가 실행된다, 이 작업은 오직 한곳에만 위치하게 된다. 이에 반하여 __cdecl은 모든 호출부에 스택을 정리하는 코드가 존재한다.
. printf()와 같은 가변인자(Variadic)함수들은 __stdcall로 바르게 사용될수 없다. 왜냐하면 오직 호출부만이 얼마나 많은 인자들이 전달되고 정리되어야 하는지 알수 있기 때문이다. 피호출부는 추정은 할수 있다 그러나 스택을 정리하는데에 함수의 로직을 검사하여야 하는데 컬링 컨벤션에서는 스스로 할수 없기 때문이다. 따라서 오직 __cdecl만이 가변인자 함수를 지원한다.
. 여기에는 사용하는데에 어떤것이 맞다 틀리다를 말하는것이 아니다. 일반적으로 스택정리는 인자 저장에 맞추어야 한다 그리고 이것은 오직 호출부와 피호출부가 무엇을 하는지 알경우에 할수 있다. 잘못된 컨벤션으로 함수를 호출하면 스택은 파괴되어 진다.
----------------------------------------------------------
* Linker symbol name decorations
위에서 언급한 대로 잘못된 컨벤션으로 함수 호출은 재난을 초래할수 있다. 그래서 MS는 이러한것을 피할수 있는 메커니즘을 가지고 있다. 이것은 잘 작동함에도 불구 하고 어떤이유로 사용하는 지를 모른다면 돌아버리게 할수도 있다.
: __cdecl (cl /Gd ...)
모든 함수의 이름은 _(underscore) 접두어가 붙는다 그리고 인자의 갯수는 신경쓰지 않는다 왜냐하면 호출부가 스택을 설정하고 정리하기 때문이다. 이것은 많은 인자가 전달될때 호출부와 피호출부를 혼란스럽게 할수 있다. 그러나 적어도 스택을 정확하게 유지 한다.
: __stdcall (cl /Gz ...)
함수의 이름에 _(underscore) 접두어가 붙는다 그리고 전달되는 인자에 대한 바이트 수가 추가로 붙는다. 이것은 잘못된 타입이나 잘못된 인자수로 호출할수 없다.
: __fastcall (cl /Gr ...)
함수의 이름 처음에 @ 그리고 접미사에 @와 인자의 바이트 수가 붙는다. __stdcall과 비슷하다.
예제)
void __cdecl foo(void); _foo
void __cdecl foo(int a); _foo
void __cdecl foo(int a, int b); _foo
void __stdcall foo(void); _foo@0
void __stdcall foo(int a); _foo@4
void __stdcall foo(int a, int b); _foo@8
void __fastcall foo(void); @foo@0
void __fastcall foo(int a); @foo@4
void __fastcall foo(int a, int b); @foo@8
추가된 이름은 절대로 C프로그램에서는 보이지 않는다는 것을 기억하자.
링크타임시 사용되어지는 이름들이다.
C> type testfile.c
extern void __stdcall func1(int a);
extern void __stdcall func2(int a, int b, double d);
extern void __cdecl func3(int b);
extern void __cdecl func4(int a, int b, double d);
int __cdecl main(int argc, char **argv)
{
func1(1);
func2(2, 3, 4.);
func3(5);
func4(6, 7, 8.0);
return 0;
}
C> cl /nologo testfile.c
testfile.c
testfile.obj : error LNK2001: unresolved external symbol _func1@4 ... __stdcall
testfile.obj : error LNK2001: unresolved external symbol _func2@16 ... __stdcall
testfile.obj : error LNK2001: unresolved external symbol _func3 ... __cdecl
testfile.obj : error LNK2001: unresolved external symbol _func4 ... __cdecl
testfile.exe : fatal error LNK1120: 4 unresolved externals
----------------------------------------------------------
* But doesn't the compiler catch this?
그렇다.
C++는 항상 지원한다. 그리고 ANSI C "function prototype"을 지원한다. 선언된 함수의 인자를 기술하는것을 가능하게 한다. 함수가 호출될때 이것은 선언된것과 비교하며 경고한다.
/* somefile.c */
extern int foo(int a); // prototype
...
n = foo(1, 2, 3); // mismatch! bad parameter count!
컴파일러는 foo()함수가 1개의 인자를 갖는것을 기대한다. MS 콜링컨벤션은 아무것도 추가 하지 않는다. 그러나 링커가 링크를 할때에 이러한 일이 일어난다. 아래 두개의 파일을 생각해 보자. 하나는 함수를 사용하고 하나는 정의를 사용한다.
------------------------
/* in file1.c */
extern int __cdecl foo(int);
...
n = foo(1);
------------------------
/* in file2.c */
int __stdcall foo(int a)
{
....
}
------------------------
컴파일러는 두개의 소스파일을 같이 보지 않는다. 여기에서 컴파일러는 잘못되어진 콜링컨벤션이 사용됨을 찾지 못한다. 링크되었을때 코드의 결과로 스택이 파괴되게 된다.
프로그래머가 이와같은 바보같은 잘못을 저지른것을 비난하기 전에 default 콜링컨벤션이 __cdecl이라는것을 생각하자. 그래서 file1.c에서 foo()에 대한 선언을 빼먹었더라도 이것은 __cdecl로 간주된다. 작은 프로젝트에서는 이 예제는 괜찮다. 그러나 시스템이 커질경우에 이러한 시추에이션은 더욱자주 일어난다. 이것은 종종 third-party(써드파티)라이브러리(많은 함수들을 익스포트 한다)에서 일어난다. 그리고 그것들은 컴파일러의 어떤 설정을 라이브러리를 만들때에 사용했는지를 항상 말하지 않는다.
Calling-convention symbol decorations exist only to maintain stack discipline
----------------------------------------------------------
* When does it matter?
대부분의 경우에, 어떠한 콜링컨벤션을 어떤 특별한 함수 혹은 전체적으로 default로 사용했는지는 별로 다를바가 없다. 그러나 __cdecl을 default로 사용하지 않을 경우에 약간의 주의점이 있다.
. main()은 항상 __cdecl이여야 한다.
. WinmMain()은 항상 __stdcall이어야 한다. 이것은 Windows.h에 선언되어 있다.
. 가변인자(printf 같은)함수들은 다르게 선언하더라도 __cdecl로 처리된다. (콜링컨벤션은 무시된다.) 컴파일러는 잘못사용 하더라도 경고를 보여주지 않는다.
int __stdcall myprintf(const char *fmt, ...); // it's really __cdecl
. 어떤 라이브러리 함수는 다른 함수의 주소를 인자로 갖는다, 그리고 이것은 정확해야 한다. 예로 qsort()는 마지막 인자로 비교함수의 주소를 갖는다. 이것은 항상 __cdecl 이어야 한다.
extern void __cdecl qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *, const void *) );
....
int __stdcall mycompare(const void *p1, const void *p2)
{
// compare here
}
....
qsort(base, n, width, mycompare); // ERROR - mismatch
----------------------------------------------------------
* Calling functions exported from DLLs
third party DLL을 사용하여(특히 다른언어로 작성된) 시스템을 빌드시 문제가 생길때 이것은 대부분 간단한 콜링컨벤션의 문제이다.(대부분 Makefile level) 만일 임포트 라이브러리와 헤더파일을 제외하고 오직 .DLL만 제공 받았을때에 콜링컨벤션에 관련하여 컴파일러는 어떠한 도움을 주지 못한다.
typedef BOOL (__stdcall *INITFUNCTION)(BOOL);
typedef int (__stdcall *DRAWFUNCTION)(int x, int y, const char *label);
HINSTANCE hInst = LoadLibrary("barcode.dll");
INITFUNCTION pfInit = (INITFUNCTION)GetProcAddress(hInst, "Init");
DRAWFUNCTION pfDraw = (DRAWFUNCTION)GetProcAddress(hInst, "Draw");
(*pfInit)(TRUE);
(*pfDraw)(1, 1, "12345");
(*pfDraw)(1, 2, "67890");
typedef을 콜링컨벤션에 사용함에 있어서 주의깊게 기억하여야 한다. 코드를 사용하는데에 컨벤션을 반드시 맞주어야 하며 잘못되었을때 컴파일러는 검출해 내지 못하고 실행시 문제가 생긴다.
여기에서는 라이브러리제공자로 부터 제공받은 문서이외에 검사할수 있는 방법이 없다.
----------------------------------------------------------
* Building bigger systems
언급했다 시피 작은 프로그램은 그렇게 많이 주의할 필요는 없다. 그러나 시스템이 커지거나 third-party 라이브러리를 사용할때 콜링컨벤션에 대하여 주의할 필요가 생긴다. 만일 non-Windows 플렛폼과 다른 콜링컨벤션으로 포팅시에 문제가 더 복잡해 질수 있다.