본문으로 바로가기
반응형

윈도우 프로그래밍을 하다 보면 cdecl이라는 코드를 종종 볼 수 있다. 구글링을 해봐도 자료가 별로 없기도 하고 아 이런 게 있구나 정도? 정리가 잘 되지 않아서 정리를 할 겸 포스팅으로 남겨두고자 한다.

 

cdecl( Caller Cleanup )과 stdcall( Standard Call )은 함수 호출 규약의 주요 유형이고 이외에도 여러 가지 유형이 있는데 일반적으로 cdecl과 stdcall이 자주 쓰이는 것으로 보인다. cdecl은 C Declaration의 약자라고 한다. 코드 상에는 어떻게 사용이 되고 있을까?

 

 

C언어 __stdcall 사용
1
2
3
4
int __stdcall MyFunc(int a, int b)
{
    return a > b;
}
 
 

우선 cdecl은 C언어의 기본이기 때문에 명시적으로 사용할 필요가 없고 stcall은 함수 앞에 __stdcall이라는 지정자를 붙여야 한다. 위에 처럼  int __stdcall MyFunc(int a, int b)라고 붙이게 된다.

 

Window API 환경 _stdcall 사용
1
2
3
4
5
6
7
8
DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
    while(1){
        WaitForSingleObject(hExitEvent, 100);
    }
 
    return 0;
}
 
 

Window API 같은 환경에서는 __stdcall 보다는 WINAPI라는 메크로를 붙인다. WINAPI라는 메크로에 정의에 대해서 들여다보면 #define WINAPI __stdcall이라고 되어있다.

 

 

호출자(caller)와 피호출자(callee)?
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
// 피호출자
int add(int a, int b) {
    return a + b;
}
 
// 호출자
int main() {
    int result = add(12);
    printf("Result: %d\n", result);
    return 0;
}
 
 

cdecl과 stdcall을 이해하기 위해서 먼저 호출자(caller)와 피호출자(callee)의 차이점에 대해서 알아야 한다. 이름만 봐서도 대충 알겠지만 함수 호출과 관련된 용어이다. 먼저 호출자(caller)는 함수를 호출하는 주체이고 피호출차(callee)는 호출되는 함수를 가리킨다. 주석에 보면 알 수 있듯이 main은 호출자가 되고 add는 피호출자가 된다.

 

 

 

__cdecl과 __stdcall 차이점

간단하게 요약을 하자면 cdecl은 호출자(caller)가 스택을 정리하는 규약이고 stdcall은 피호출자(callee)가 스택을 정리하는 규약이다. 2가지 호출 규약에 대해서 대략적으로 정리를 해보면 다음과 같다.

 

1. C의 경우 하위 레벨의 언어이기 때문에 상대적으로 이식성도 높은 편

 

2. 함수 호출 시 cdecl은 오른쪽에서 왼쪽으로 스택에 푸시를 하고 stdcall은 왼쪽에서 오른쪽으로 스택에 푸시

1
int myFunc(int a, double b, float c);
 

위에 함수는 인자를 3개를 갖는 함수이다. 왼쪽에서 오른쪽으로 스택에 푸시를 하게 되는 경우 int a를 전달하고 double b를 전달하고 그다음에 float c를 전달하게 되고 오른쪽에서 왼쪽으로 스택에 푸시를 하는 경우에는 이와 반대라고 생각하면 된다.

 

3. 매개 인자에서 차이

stdcall은 매개 인자가 고정이고 cdecl은 매개인자가 가변이다. 여기서 궁금한 점이 생기는 게 그럼 컴파일을 하는 순간에 매개인자의 수를 알 수 있지 않나라는 생각을 할 수 있을 것이다. 예외의 상황이 발생하는 경우가 있는데 예를 들어 루프를 돌게 되는 경우 컴파일을 하는 순간 개수를 알 수 있을까? 알 수가 없다. 이런 상황에서 cdecl은 유연성을 제공하게 된다.

 

4. 약간의 성능 차이 발생

호출자(caller)가 스택을 정리하게 되면 함수가 추가 작업을 수행하지 않아도 되기 때문에 이러한 차이가 발생할 수 있다. 이는 환경에 따라서 다를 수 있고 거의 미미하다고 보면 될 것 같다. 이로 인해서 cdecl 보다 stcall에서 함수의 크기가 더 작아질 수 있다.

 

 

 

함수 호출 규약을 맞춰야 하는 이유?

cdecl로 만든 어떤 함수를 사용한다고 가정했을 때 stdcall로 호출을 하게 되면 어떤 문제가 발생하게 될까? 스택 정리 시에 문제가 발생을 할 수 있고 언어 간이나 라이브러리 간에 유기적으로 운용할 때 문제가 발생을 할 수가 있다.

 

하나의 언어 혹은 환경에서 작성된 코드에서는 문제없이 잘 사용하고 있었는데 서로 다른 환경의 함수를 호출하다 보니 해당 문제가 발생을 하여서 정리를 해보았다.

반응형