개발이야기/CSAPP

[CSAPP] 함수 호출의 구조 – 스택 프레임과 재귀

Study & Stack 2025. 5. 30. 00:41
728x90

1. 개요

이번 회차에서는 함수 호출이 어셈블리에서 어떻게 작동하는지를 알아본다.
C 함수는 호출되면 **스택 프레임(Stack Frame)**을 구성하여 인자, 지역 변수, 리턴 주소 등을 저장하고, call, ret 명령어로 흐름을 제어한다.


2. 함수 호출과 스택 프레임 구조

2.1 함수 호출 기본 구조

int sum(int x, int y) {
    return x + y;
}

→ 어셈블리 예시:

sum:
    pushq %rbp            # 이전 프레임 포인터 저장
    movq %rsp, %rbp       # 새 프레임 기준 설정
    movl %edi, -4(%rbp)   # 첫 번째 인자 x
    movl %esi, -8(%rbp)   # 두 번째 인자 y
    movl -4(%rbp), %eax
    addl -8(%rbp), %eax
    popq %rbp
    ret
  • %rbp: 현재 스택 프레임의 기준
  • %rsp: 현재 스택 최상단
  • pushq, popq: 프레임 저장 및 복구

💡 컴파일러는 각 변수에 대해 rbp 기준 오프셋을 할당한다.

2.2 호출 규약 (Calling Convention)

x86-64 System V 규약에 따라:

  • 첫 6개 정수 인자: %rdi, %rsi, %rdx, %rcx, %r8, %r9
  • 추가 인자: 스택에 저장됨
  • 리턴 값: %rax
int f(int a, int b, int c, int d, int e, int f, int g);

→ 어셈블리에서:

  • %rdi = a
  • %rsi = b
  • %rdx = c
  • %rcx = d
  • %r8 = e
  • %r9 = f
  • stack = g 이후 인자

3. 재귀 함수 호출

3.1 재귀 함수 예시

long fact(long n) {
    if (n <= 1) return 1;
    return n * fact(n - 1);
}

→ 어셈블리 핵심 흐름:

fact:
    pushq %rbp
    movq %rsp, %rbp
    cmpq $1, %rdi         # n <= 1 ?
    jle .Lbase_case
    movq %rdi, -8(%rbp)   # 저장 n
    subq $1, %rdi         # n - 1
    call fact             # 재귀 호출
    imulq -8(%rbp), %rax  # n * fact(n-1)
    popq %rbp
    ret
.Lbase_case:
    movl $1, %eax
    popq %rbp
    ret
  • 호출 시마다 새로운 스택 프레임이 생성됨
  • 리턴 주소와 인자, 지역 변수 모두 스택에 보관됨
  • 재귀 깊이가 깊어질수록 스택 프레임이 누적됨

⚠️ 스택 오버플로우(stack overflow)는 재귀 깊이가 너무 깊어져 스택이 가득 찰 때 발생함


4. 요약 정리

  • 함수 호출 시 call, ret 명령어로 흐름을 제어하며, 스택을 통해 프레임을 구성한다
  • 함수 인자와 지역 변수는 스택 프레임 내에서 오프셋을 통해 접근된다
  • x86-64 호출 규약에 따라 첫 6개 인자는 레지스터, 이후는 스택으로 전달된다
  • 재귀 함수는 매 호출마다 새로운 프레임을 쌓으며, 리턴 시 역순으로 복귀한다

728x90