개발이야기/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