티스토리 뷰

반응형

 

🧭 함수 호출 규약과 __stdcall — 왜 중요하고, 어떻게 동작하는가?

프로그래밍을 하다 보면 __stdcall, __cdecl, __fastcall 같은 낯선 키워드를 마주하게 됩니다. 이들은 단순한 함수 선언의 장식이 아니라, **함수를 호출하고 실행하고 마무리하는 방식 자체를 규정하는 규약(calling convention)**입니다.
그중에서도 특히 __stdcall은 Windows API, DLL, COM 등에서 널리 쓰이는 매우 중요한 호출 규약입니다. 하지만 "누가 스택을 정리하느냐"는 설명은 자칫 추상적일 수 있습니다. 그래서 이 글에서는 다음 질문에 답하며 개념을 명확히 잡고자 합니다:


🧩 질문 1: __stdcall이 정확히 뭐죠?

__stdcall은 C/C++에서 함수 호출 시 사용되는 함수 호출 규약(calling convention) 중 하나입니다.
"규약"이란 말 그대로 약속된 방식을 의미하며, 다음 세 가지를 정의합니다:

  1. 함수 인자를 어떤 순서로 스택에 올릴지
  2. 스택을 함수 호출 후 누가 정리할지
  3. 함수 이름을 컴파일 후 어떻게 변경할지

🧩 질문 2: "누가 스택을 정리한다"는 게 무슨 뜻인가요?

이 질문이 핵심입니다. 대부분의 사람이 여기서 막힙니다.
"결국은 컴퓨터가 알아서 처리하는 거 아닌가? 누가 정리하든 뭐가 문제야?"
이건 스택(Stack) 의 구조와 함수 호출 과정을 이해하면 명확해집니다.

📦 함수 호출은 스택을 기반으로 한다

함수 호출 시, 컴퓨터는 다음과 같은 절차를 밟습니다:

int result = add(10, 20);

실제로는 다음과 같은 순서로 일어납니다:

  1. 20 → 스택에 푸시
  2. 10 → 스택에 푸시
  3. add() 함수 호출 → call 명령어
  4. 함수 내부에서 계산 수행
  5. 함수 리턴 → 결과는 레지스터에 저장됨
  6. 스택에 쌓은 10, 20을 정리해야 함

이제 누가 그 쌓은 값들을 치울지를 정해야 합니다.


🛠️ 두 가지 방식: __cdecl vs __stdcall

항목 __cdecl __stdcall

인자 순서 오른쪽 → 왼쪽 오른쪽 → 왼쪽
스택 정리 호출한 쪽(caller) 함수 자신(callee)
이름 변경 _함수명 _함수명@인자바이트수
가변 인자 함수 지원 가능 불가능

예를 들어 설명:

int __stdcall add(int a, int b);

호출부 (main 함수)

push 20
push 10
call add   ; add 함수가 스택까지 직접 정리하고 돌아옴

add 함수 내부에서는:

mov eax, [esp+4]   ; a = 10
mov edx, [esp+8]   ; b = 20
add eax, edx
ret 8              ; 돌아가면서 스택 8바이트 정리함

반면, __cdecl이라면:

push 20
push 10
call add
add esp, 8         ; 호출한 쪽에서 스택 정리

🧨 왜 이게 중요할까? "아무나 치우면 되는 거 아냐?" → 안 됩니다

컴파일러는 이 규약을 기준으로 어셈블리 코드를 생성합니다.
__stdcall이라고 지정하면 ret 8이 함수에 들어가고,
__cdecl이라고 지정하면 add esp, 8이 호출한 쪽에 들어갑니다.

그러므로 규약을 어기면 다음과 같은 문제가 생깁니다:

❌ 스택을 두 번 정리하면?

  • 예: 함수도 ret 8로 치우고, 호출한 쪽도 add esp, 8로 또 치움
  • 결과: 스택 포인터가 잘못됨 → 다음 함수 호출 시 엉뚱한 값 접근 → 크래시

❌ 스택을 아무도 안 치우면?

  • 쓰레기값이 계속 쌓임 → 스택 오버플로우 발생 → 프로그램 종료

이런 이유 때문에 "누가 정리할지는 반드시 정확히 정해야 하고, 맞춰야" 합니다.


🚀 실전에서 __stdcall은 왜 유용할까?

  1. DLL, Windows API:
    • API 함수는 외부에서 누가 호출할지 모르기 때문에, 자기 내부에서 스택을 정리하는 것이 더 안전합니다.
    • 예: MessageBox, CreateWindow 등 WinAPI는 전부 __stdcall 사용
  2. 코드 사이즈 최적화:
    • __stdcall은 함수가 직접 정리하므로 호출부에 add esp, N이 반복해서 생기지 않음 → 코드 크기 감소
  3. 성능 차이?
    • 실제 실행 속도는 거의 차이 없음
    • 다만, 코드 사이즈가 줄어들 수는 있음 (특히 반복 호출 많은 경우)
  4. 가변 인자 함수는 반드시 __cdecl:
    • printf("x = %d", x); 같은 함수는 인자 개수를 함수가 모르기 때문에,
    • 호출한 쪽이 정리해야만 동작함 → __stdcall은 사용 불가

🧾 요약 정리

항목 __cdecl __stdcall

스택 정리 호출한 쪽 함수 자신
사용처 일반 함수, 가변 인자 WinAPI, DLL, COM
이름 변화 _함수명 _함수명@바이트수
장점 유연성, printf 가능 안정성, DLL 호환성
단점 호출부마다 스택 정리 코드 가변 인자 지원 불가

📌 결론

함수 호출 규약은 컴파일러와 CPU가 어떻게 함수 호출을 처리할지를 결정하는 계약입니다.
__stdcall은 그중에서도 DLL이나 외부 인터페이스에서 가장 안전한 방식으로 널리 쓰이고 있으며,
"스택을 누가 정리하느냐"는 단순한 표현 뒤에는 컴파일러가 만들어내는 어셈블리 코드의 차이
실행 중 발생할 수 있는 치명적인 오류를 방지하기 위한 설계 철학이 담겨 있습니다.


.

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함
반응형