스택으로 이해하는 함수 호출 규약

두부4등분·2025년 11월 1일

우리가 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. 스택 정리
이때 "스택을 누가 정리하느냐"가 규약마다 다르다. cdeclstdcall을 설명하며 함께 설명하도록 하겠다.

cdecl

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

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)
대/소문자 변환 규칙대/소문자 변환은 수행되지 않음

0개의 댓글