컴퓨터 구조 기초 - 3
스택 프레임
함수 내 선언된 변수는 스택에 할당됨.
함수 호출 과정에서 할당되는 메모리 블록(지역변수 선언 등으로 할당되는)을 가리켜 스택 프레임이라 한다.
스택프레임은 함수에 종속된다. 함수가 종료되면 스택프레임이 반환된다.
.
sp 레지스터
스택을 쌓기 위해서는 현재까지 저장한 데이터의 위치를 알아야겠지? 이를 기억하기 위해 CPU내에 sp(stack pointer)레지스터가 존재함.
스택 프레임이 반환되면 sp레지스터를 움직여야 하는데, 문제는 얼마나 움직여야 하는지를 모른다. 그래서 또 이를 기억하기 위해 프레임 포인터(FP)가 존재한다.
.
프레임 포인터
스택프레임이 반환되면 sp를 fp위치로 맞추면 그만임. 그럼 fp값들은 어디에 보관?
함수가 호출되면 그때 스택에 fp값을 저장함.
반환될 때는 스택에 있는 fp값을 fp레지스터에 로드하면 끝.
.
함수? 프로시저?
함수 호출과 프로시저 호출을 구분할 수 있는데, 입력에 대한 반환값이 존재하면 함수 호출, 아니라면 반환 값 없는 서브 루틴의 실행을 위한 호출 = 프로시저 호출이라 한다 .
.
함수 호출 인자의 전달방식
함수 호출 시 전달되는 인자를 어디에다 둘 것인가도 CPU마다 다름
보통은 스택에 저장된다 해도 무리는 아니지만 성능 향상을 위해서 일부 인자들은 레지스터에 저장되도록 만들기도 함.
.
PUSH & POP 명령어
스택에 변수를 할당할 때, 스택에 값을 저장하고 sp값을 증가시킨다.
이건 어셈블리로 어떻게 구현?
예를 들어 sp가 가리키는 위치에 함수 전달인자인 7을 저장한다 해보자.
STORE 7 sp 같은 건안 됨.
첫 번째는 반드시 레지스터여야 하고, 두 번째는 메모리 주소가 와야 함.
그러면
ADD r1, 7, 0 (혹은 MOV r1 7)
이런 식으로 스택에 저장할 값을 저장해 둬서 첫 번째에 레지스터가 오게 만들 수 있음.
두 번째에는 메모리 주소가 와야 하는데,
이걸 위해서 SP가 가리키는 값을 저장할 임의의 메모리 주소를 정해야 함. 0x10이라 하자.
STORE sp, 0x10
이런 식으로 sp의 값을 저장하고
STORE r1 [sp]
이렇게 indirect 모드를 쓰면? 7이라는 값을 마침내 스택에 저장할 수 있게 된다.
그다음 sp를 증가시키는 연산은? 정수형은 4바이트이니까
ADD sp, sp, 4
한 번에 나타내면..
ADD r1, 7, 0
STORE sp, 0x10
STORE r1, [0x10]
ADD sp, sp, 4
이걸 모아서
PUSH 7
이렇게 만들 수 있겠지?
POP명령어는 그냥 아래와 같은 의미임..
ADD sp, sp, -4
인자 없이
POP
이렇게 쓸 수 있음.
이제 함수가 호출되면 fp값을 스택에 넣어야 하니까
PUSH fp
그리고 fp를 스택에 올리기 전 위치에 sp를 놓고
ADD fp, sp, -4
그다음 함수 전달인자를 넣어주면 된다.
PUSH 7
.
호출할 함수의 위치
코드 영역은 프로그램의 코드(컴파일된 명령어들의 집합)가 올라가 있는 위치임.
그리고 현재까지 실행한 명령어의 위치를위치를 기억하는 게 PC 레지스터
PC는 CPU에서 Fetch 할 때마다 증가함.
음 근데 if문 등을 이용해서 명령어 위치를 막 넘어갈 수 있는데 PC하나 됨? ㄴㄴ
호출을 끝내고 나면 다시 원래 위치로 돌아와야 할 거임.
그럼 이 원래 위치는 어디에 쓸까? 스택에 쓴다.
.
함수 호출규약(calling convention)
함수 호출 시 인자를 전달하는 방식과 스택프레임을 반환하는 방식을 약속해 놓은 것을 함수 호출규약이라 함.
__cdecl, __stdcall
이런 애들이 있는데..
int __stdcall Foo(int a)
이런 식으로 쓸수 있음.
어? 나는 저런거 써본 적 없는데? 그건 디폴트 값이 존재하기 때문임. (프로젝트 속성)
아무튼 이런 함수 호출규약을 통해 인자의 전달방식 (레지스터를 쓸 건지 말건지)를 정해줄 수도 있고.
스택프레임을 호출자가 정리할지 아니면 호출된 함수가 반환할지도 정해줄 수 있음.