우리가 C코드에서
int result = add(2, 3);
이런 코드를 쓸 때 겉으로는 단순히 함수를 호출하는 한 줄이지만, CPU 입장에서는 스택 조작, 레지스터 관리, 복귀 주소 저장 등 복잡한 절차가 일어난다.
이때 이 복잡한 과정을 누가, 어떤 순서로, 어떤 규칙에 따라 처리할지 정확히 정한 약속이 없으면 프로그램이 제대로 동작할 수 없다.
이 약속이 바로 함수 호출 규약이다.
보통 "간단한 프로그래밍 문제를 풀 때는 내 컴퓨터 1개인데 저런 규약이 왜 필요한가?" 라는 궁금증이 생겼다.
이 궁금증에 대한 대답은 이렇게 답할 수 있다.
프로그램 하나는 하나의 컴파일러로만 만들어지지 않는다.
예를 들어
#include <stdio.h>
#include <windows.h>
int main() {
printf("Hello from C Standard Library!\n"); // libc (cdecl)
MessageBoxA(NULL, "Hi", "Windows API", 0); // WinAPI (stdcall)
return 0;
}
이러한 코드가 실행될 때 CPU는
1. main()은 GCC가 만든 규약(cdecl)으로 실행된다.
2. printf()는 C 표준 라이브러리 쪽에서 정의한 함수이다. 이 또한 cdecl을 따른다.
3. MessageBoxA()는 Window OS 내부의 DLL 함수이므로 stdcall 규약을 따른다.
즉, 같은 프로그램 안에서도 서로 다른 호출 규약의 함수가 공존한다.
여기서
프로그램에는 1개 이상의 다양한 함수가 존재하는데, 일반적으로 함수들은 각각 함수호출규약을 가진다. 순차적으로 프로그램이 실행되며 스택에는 각 함수에 알맞는(함수호출규약에 따라) 스택프레임이 생성된다. 따라서 오류가 나지 않는다.

출처:김놀자(티스토리) - Stack Frame과 함수 호출 규약(__stdcall)
add(2,3)같은 함수를 호출할 때, CPU는 다음 순서로 동작한다.
1. 인자들을 스택에 push
2. call 명령으로 함수 진입
3. 함수 내부에서 새로운 스택프레임 생성
4. 함수 실행 후 ret 명령으로 복귀
5. 스택 정리
이때 "스택을 누가 정리하느냐"가 규약마다 다르다. cdecl과 stdcall을 설명하며 함께 설명하도록 하겠다.
cdecl은 C/C++ 프로그램에 대한 호출규약이다. 함수를 호출한 호출자(caller)가 스택을 정리하는 규약이다.
예시 C코드
int add(int a, int b) {return a+b;}
int main() {add(2,3);}
push 3 ; 두번째 인자
push 2 ; 첫번째 인자
call add ; 함수호출(복귀 주소 push)
add esp, 8 ; 호출자가 스택 정리(8바이트 제거)
특징
| 항목 | 내용 |
|---|---|
| 인수 전달 순서 | 오른쪽에서 왼쪽 |
| 스택 유지 관리 책임 | 호출하는 함수(caller)가 스택에서 인수를 꺼냄 |
| 이름 데코레이션 규칙 | C 링크를 사용하는 __cdecl 함수를 내보낼 경우를 제외하고, 밑줄(_)이 이름 앞에 접두사로 붙음 |
| 대/소문자 변환 규칙 | 대/소문자 변환은 수행되지 않음 |
stdcall은 주로 Windows API에서 사용되는 함수 호출 규약이다.
cdecl과 달리, 피호출자(callee) 가 스택을 정리한다.
즉, 함수를 호출한 쪽에서는 스택을 복원하지 않아도 된다.
정해진 인자 개수를 가진 함수에서 효율적이다.
예시 C코드
int __stdcall add(int a, int b) { return a + b; }
int main() { add(2, 3); }
어셈블리 동작
push 3 ; 두 번째 인자
push 2 ; 첫 번째 인자
call add ; 함수 호출 (복귀 주소 push)
; 호출자는 스택 정리하지 않음
add() 함수 내부:
mov eax, [ebp+8] ; a
mov ecx, [ebp+12] ; b
add eax, ecx
ret 8 ; 피호출자가 스택 정리 (8바이트 제거)
특징
| 항목 | 내용 |
|---|---|
| 인수 전달 순서 | 오른쪽에서 왼쪽 |
| 스택 유지 관리 책임 | 피호출자(callee)가 스택에서 인수를 꺼내며 정리 |
| 이름 데코레이션 규칙 | 이름 앞에 밑줄(_)이 붙고, 인자의 총 바이트 크기를 @ 뒤에 표시 (예: _add@8) |
| 대/소문자 변환 규칙 | 대/소문자 변환은 수행되지 않음 |