티스토리 뷰
🧭 함수 호출 규약과 __stdcall — 왜 중요하고, 어떻게 동작하는가?
프로그래밍을 하다 보면 __stdcall, __cdecl, __fastcall 같은 낯선 키워드를 마주하게 됩니다. 이들은 단순한 함수 선언의 장식이 아니라, **함수를 호출하고 실행하고 마무리하는 방식 자체를 규정하는 규약(calling convention)**입니다.
그중에서도 특히 __stdcall은 Windows API, DLL, COM 등에서 널리 쓰이는 매우 중요한 호출 규약입니다. 하지만 "누가 스택을 정리하느냐"는 설명은 자칫 추상적일 수 있습니다. 그래서 이 글에서는 다음 질문에 답하며 개념을 명확히 잡고자 합니다:
🧩 질문 1: __stdcall이 정확히 뭐죠?
__stdcall은 C/C++에서 함수 호출 시 사용되는 함수 호출 규약(calling convention) 중 하나입니다.
"규약"이란 말 그대로 약속된 방식을 의미하며, 다음 세 가지를 정의합니다:
- 함수 인자를 어떤 순서로 스택에 올릴지
- 스택을 함수 호출 후 누가 정리할지
- 함수 이름을 컴파일 후 어떻게 변경할지
🧩 질문 2: "누가 스택을 정리한다"는 게 무슨 뜻인가요?
이 질문이 핵심입니다. 대부분의 사람이 여기서 막힙니다.
"결국은 컴퓨터가 알아서 처리하는 거 아닌가? 누가 정리하든 뭐가 문제야?"
이건 스택(Stack) 의 구조와 함수 호출 과정을 이해하면 명확해집니다.
📦 함수 호출은 스택을 기반으로 한다
함수 호출 시, 컴퓨터는 다음과 같은 절차를 밟습니다:
int result = add(10, 20);
실제로는 다음과 같은 순서로 일어납니다:
- 20 → 스택에 푸시
- 10 → 스택에 푸시
- add() 함수 호출 → call 명령어
- 함수 내부에서 계산 수행
- 함수 리턴 → 결과는 레지스터에 저장됨
- 스택에 쌓은 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은 왜 유용할까?
- DLL, Windows API:
- API 함수는 외부에서 누가 호출할지 모르기 때문에, 자기 내부에서 스택을 정리하는 것이 더 안전합니다.
- 예: MessageBox, CreateWindow 등 WinAPI는 전부 __stdcall 사용
- 코드 사이즈 최적화:
- __stdcall은 함수가 직접 정리하므로 호출부에 add esp, N이 반복해서 생기지 않음 → 코드 크기 감소
- 성능 차이?
- 실제 실행 속도는 거의 차이 없음
- 다만, 코드 사이즈가 줄어들 수는 있음 (특히 반복 호출 많은 경우)
- 가변 인자 함수는 반드시 __cdecl:
- printf("x = %d", x); 같은 함수는 인자 개수를 함수가 모르기 때문에,
- 호출한 쪽이 정리해야만 동작함 → __stdcall은 사용 불가
🧾 요약 정리
항목 __cdecl __stdcall
스택 정리 | 호출한 쪽 | 함수 자신 |
사용처 | 일반 함수, 가변 인자 | WinAPI, DLL, COM |
이름 변화 | _함수명 | _함수명@바이트수 |
장점 | 유연성, printf 가능 | 안정성, DLL 호환성 |
단점 | 호출부마다 스택 정리 코드 | 가변 인자 지원 불가 |
📌 결론
함수 호출 규약은 컴파일러와 CPU가 어떻게 함수 호출을 처리할지를 결정하는 계약입니다.
__stdcall은 그중에서도 DLL이나 외부 인터페이스에서 가장 안전한 방식으로 널리 쓰이고 있으며,
"스택을 누가 정리하느냐"는 단순한 표현 뒤에는 컴파일러가 만들어내는 어셈블리 코드의 차이와
실행 중 발생할 수 있는 치명적인 오류를 방지하기 위한 설계 철학이 담겨 있습니다.
.
'프로그래밍 > 시스템 프로그래밍' 카테고리의 다른 글
API란 무엇인가? (0) | 2025.04.05 |
---|---|
CreateThread()와 _beginthreadex() 차이점 (0) | 2025.04.05 |
프로세스와 스레드 (0) | 2025.03.31 |
파이프 방식의 IPC (0) | 2025.03.27 |
프로세스, 핸들 테이블, 그리고 핸들 상속 (0) | 2025.03.26 |
- Total
- Today
- Yesterday
- 티스토리챌린지
- 회계
- 일문따
- 백준
- 통계학
- 인프런
- 데이터분석
- C
- 열혈프로그래밍
- 사회심리학
- 코딩테스트
- 보세사
- 일본어
- 뇌와행동의기초
- 강화학습
- 심리학
- 여인권
- 정보처리기사
- 오블완
- 윤성우
- stl
- 인지부조화
- 파이썬
- c++
- Python
- 일본어문법무작정따라하기
- 통계
- C/C++
- K-MOOC
- 류근관
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |