메모리 구조를 알아보면서 지역 변수와 함수 호출에 필요한 정보들이 주로 STACK영역에 저장된다는 것을 알게 되었다.
그렇다면 함수를 하나 호출할 때 스택에는 정확히 어떤 일이 일어날까?
프로그램은 함수가 호출될 때마다 함수가 실행되는 동안 필요한 정보(지역변수 등)를 저장하기 위해 스택의 일부 공간을 사용한다.
하나의 함수 호출을 위해 사용되는 스택 영역의 한 구간을 일반적으로 스택 프레임(Stack Frame)이라고 부른다.
// foo.c void foo(void) { int a = 10; int b = 20; } int main(void) { foo(); return 0; }
main()도 함수니까 스택 프레임에 올라간다. 실행 도중에 foo()를 호출하면 foo()를 위한 공간이 스택에 마련된다.
높은 주소 ┌──────────────────────┐ │ main함수 stack frame│ ├──────────────────────┤ │ return address │ ├──────────────────────┤ │ foo의 지역 변수 b │ │ foo의 지역 변수 a │ │ ... │ └──────────────────────┘ 낮은 주소
x86-64와 같은 일반적인 환경에서는 스택이 높은 주소에서 낮은 주소 방향으로 증가하기 때문에, 새로운 함수가 호출되면 스택 포인터가 더 낮은 주소 쪽으로 이동하면서 새로운 공간을 확보한다.
스택 포인터?
메모리 스택에서 중요한 레지스터가 rsp와 rbp이다.
rsp(Stack Pointer)는 현재 스택의 Top을 가르킨다. 함수가 지역 변수를 할당할 공간을 위해 스택 공간을 더 확보하면 rsp의 값이 변경된다.
rbp(Base Pointer)는 현재 함수의 스택 프레임의 시작점을 가르킨다.
int func() { return 40; }이 코드를
disass하면 아래와 같이 나온다.
rbp값을 스택에 보관한다.rsp를 새로운 rbp로 설정한다.rbp가 함수의 스택 프레임을 나타내는 하나의 기준점이 된다.rsp를 0x30만큼 낮은 주소로 이동시킨다.0x30 = 48바이트만큼의 스택 공간을 확보하게 된다.높은 주소
이전 함수의 stack frame
──────────────────────
rbp+8 return address
rbp 이전 rbp
──────────────────────
rbp-8 지역 변수
rbp-16 지역 변수
rbp-24 지역 변수 ...
rbp-0x50
──────────────────────
rsp
낮은 주소
함수가 끝난다면 스택 프레임은 어떻게 될까?
함수 실행이 끝나면 해당 스택 프레임은 더 이상 사용하지 않는다.
해당 함수가 스택 프레임을 정리하고 나간다고 생각하면 된다.
일반적인 함수 종료 과정은 다음과 같다.
mov %rbp, $rsp
pop %rbp
ret
혹은 이러한 스택 프레임 복구를 한 번에 수행하는
leave 또는 ret형태의 명령이 사용될 수도 있다.
위의 사진에서는
pop %rbp 이후에 ret명령으로 스택 프레임을 정리하고 있다.
아래 사진에서는 leave와 ret명령으로 스택을 반납하는 모습이다.
이 과정에서 핵심은 함수가 종료됐다고 해서 해당 메모리 공간이 즉시 초기화되거나 물리적으로 사라지는게 아니라는 점이다.
스택 포인터와 베이스 포인터를 옮기면서 사용하는 영역이 달라질 뿐이다.
예를 들어 foo()에서 지역 배열 변수를 사용한다고 해 보자.
//foo.c
void foo(void)
{
char buffer[64];
// ...
}
foo()가 실행되는 동안 buffer은 foo()의 스택 프레임 내부에 올라온다.
foo 실행 중
┌──────────────────────┐
│ return address │
├──────────────────────┤
│ old rbp │
├──────────────────────┤
│ buffer[64] │ ← 유효한 메모리
└──────────────────────┘
하지만 foo()가 반환될 때 해당 영역은 더이상 foo()의 것이 아니다. rbp와 rsp를 옮기고 공간을 반납한다.
┌──────────────────────┐
│ │
│ 다시 사용할 수 있는 │
│ 스택 공간 │ ← 이전에는 buffer가 있던 위치
│ │
└──────────────────────┘
개념적으로 나타내면 위처럼 표현되겠다.
함수 반환 직후에는 메모리에 과거의 값이 잠시 남아있을 수 있다.
또 다른 함수가 호출되면 다시 rsp rbp를 옮겨 스택을 사용하기 때문에 같은 스택 공간을 사용할 수도 있다.
rsp와 rbp로 관리된다.rsp와 rbp를 다시 되돌리며 스택 프레임을 반납한다.