함수 호출 규약 (Calling Convention)
함수 호출 규약이란 스택 프레임을 반환하는 방법에 대한 약속이다.
스택 프레임 반환이란 SP 복원, FP 복원 등의 작업을 말하는데, 핵심은 이 반환의 주체가 누구냐는 것이다.
- Caller (호출자): 함수를 호출한 쪽
- Callee (피호출자): 호출된 함수 자체
대표적인 호출 규약
| 규약 | 스택 정리 주체 | 특징 |
|---|---|---|
__cdecl | Caller | C언어 기본 방식. 호출자가 스택 프레임을 반환 |
__stdcall | Callee | 호출된 함수 내부에서 스택 프레임을 반환 |
__fastcall | Callee | 인자를 레지스터에 저장 (인자가 4개 초과 시 나머지는 스택 사용) |
__cdeclvs__stdcall비교
__cdecl은 호출자가 정리하기 때문에 가변 인자 함수(printf등) 구현이 가능하다.
__stdcall은 피호출자가 정리하기 때문에 코드 크기가 작아지는 장점이 있다.
멀티스레드와 함수 호출 규약
스레드와 스택의 관계
멀티스레드 환경에서 각 스레드는 자신만의 스택을 독립적으로 할당받는다.
- 스레드 A의 함수 호출 흐름과 스레드 B의 함수 호출 흐름은 완전히 별개의 스택 위에서 동작한다.
- 즉, 스택 프레임의 생성/해제가 스레드별로 독립적으로 일어난다.
호출 규약이 중요한 이유
스레드 함수는 OS가 직접 호출한다.
즉, Caller가 내가 작성한 코드가 아니라 OS 자체다.
Windows 기준으로 CreateThread는 __stdcall 규약으로 스레드 함수를 호출하도록 설계되어 있다.
DWORD WINAPI MyThreadFunc(LPVOID param) { ... }
// ↑ WINAPI == __stdcall규약이 어긋나면 어떻게 되는가
스레드 함수를 __cdecl로 잘못 선언한 경우:
OS가 __stdcall 기준으로 호출
→ Callee(내 함수)가 스택을 정리할 것이라 기대
→ 내 함수는 __cdecl이므로 Caller(OS)가 정리해야 한다고 판단
→ 아무도 스택을 정리하지 않음
→ 스택 오염 💥
반대로 OS가 이미 정리했는데 Callee도 정리하면 이중 해제가 발생한다.
멀티스레드에서 호출 규약이 중요한 이유는
Caller가 내 코드가 아닌 OS이기 때문이다.
OS와 내 함수의 규약이 일치해야 스택 정리 주체가 맞아떨어지고,
어긋나면 스택이 오염되어 프로그램이 비정상 종료된다.