스택 메모리는 후입선출(LIFO, Last In First Out) 구조로 되어 있으며, 함수 호출 시 다음과 같은 요소들이 저장됩니다.
함수가 호출되면 새로운 스택 프레임(Stack Frame) 이 생성되고, 함수가 종료되면 해당 스택 프레임이 해제됩니다.
스택은 높은 메모리 주소에서 낮은 메모리 주소 방향으로 성장하며, 함수가 호출될 때마다 새로운 스택 프레임이 생성됩니다.
#include <iostream>
using namespace std;
void Func3(float a) {
cout << "Func3" << endl;
}
void Func2(int a, int b) {
cout << "Func2" << endl;
Func3(10);
}
void Func1() {
cout << "Func1" << endl;
Func2(1, 2);
}
int main() {
cout << "main" << endl;
Func1();
return 0;
}
위 코드를 실행할 때 스택 프레임이 어떻게 생성되고 관리되는지 어셈블리 코드를 분석하여 확인해보겠습니다.
00312711 mov eax,dword ptr [b]
00312714 push eax
00312715 mov ecx,dword ptr [a]
00312718 push ecx
b와 a 변수를 push 명령어를 통해 스택에 저장.MultiplyBy 함수의 매개변수로 사용될 수 있도록 준비됨.00312719 call MultiplyBy (0311118h)
MultiplyBy 함수 호출 (call 명령어).MultiplyBy 함수로 이동.0031271E add esp,8
esp를 조정하여 스택에서 인자 해제 (8바이트 해제, int 2개).00312736 mov esp,ebp
00312738 pop ebp
00312739 ret
ebp를 복원하여 스택 프레임 해제.ret 명령어 실행 후, 저장된 리턴 주소로 복귀.디버깅을 활용하여 함수 호출 과정에서 스택의 변화를 추적할 수 있습니다.
| 키 | 설명 |
|---|---|
F5 | 디버깅 시작 |
F10 | 함수 실행 결과만 확인 |
F11 | 함수 내부 코드까지 한 단계씩 실행 |
push 명령어를 통해 a와 b 값이 스택에 저장됨.| ESP 주소 | 데이터 |
|---|---|
| 0x00AFF744 | b = 5 |
| 0x00AFF740 | a = 3 |
call MultiplyBy) 직후, 리턴 주소가 스택에 저장됨.| ESP 주소 | 데이터 |
|---|---|
| 0x00AFF73C | 리턴 주소 (0x007B2690) |
| 0x00AFF740 | a = 3 |
| 0x00AFF744 | b = 5 |
int MultiplyBy(int a, int b) {
int c = a * b; // 지역 변수 c
return c;
}
c의 값이 계산되고 스택에 저장됨.| ESP 주소 | 데이터 |
|---|---|
| 0x00AFF738 | 지역 변수 c = 15 |
| 0x00AFF73C | 리턴 주소 |
| 0x00AFF740 | a = 3 |
| 0x00AFF744 | b = 5 |
pop 명령어를 사용하여 스택을 정리하고, ret을 실행하여 원래 실행 위치로 복귀.push 시 감소, pop 시 증가.ebp를 사용하여 매개변수와 지역 변수 접근.ESP는 계속 변하지만 EBP는 상대 주소 계산을 위해 고정됨.Ctrl + Alt + G: 레지스터 창 열기.이 자료를 바탕으로 다시 학습하면 스택 프레임과 함수 호출의 원리를 확실히 이해할 수 있습니다! 🚀