본문 바로가기

IT개발/컴퓨터구조

컴퓨터 구조에 대한 설명(3-1) 절차적 함수 호출(Procedure Call)지원 CPU모델

자, 지난 주에 배운 내용으로 CPU의 연산방식에 대한 궁금증은 어느정도 해소되었을 것이다.

하지만 이것을 우리가 구현한 응용프로그램과 연결시켜 생각하기에는 좀 부족한 감이 있다.

 

왜냐하면, C, C++같은 언어는 함수의 연속된 호출을 통해서 프로그램 흐름을 형성하기 때문이다.

 

=> 따라서, 함수호출이 어떻게 이루어지는지 그 과정을 이해해야만 함수호출과 더불어 CPU 연산방식을 보다 깊게 이해할 수 있다.

 

 

목표 : 함수가 호출되는 원리와 호출이 될 때마다 할당되는 메모리 관리방식에 대한 이해

 

 

# 절차적 함수 호출(Procedure Call)지원 CPU모델

 

우리가 쉽게 생각하는 함수호출도 CPU의 도움을 받아야만 가능함.

(함수 호출이라는 기능은 하드웨어 종속적인 부분이 상당수 존재함)

 

 

- 스택 프레임(Stack Frame)구조

 

함수 호출 과정에서 할당되는 메모리 블록(지역변수의 선언으로 인해 할당되는 메모리 블록)을 의미함.

 

void a(void)

{

   int e = 5;

   int h = 6;

   ...

}

 

ex) "함수 a가 호출되면 이 함수에 선언된 e,h가 스택에 할당되는데, 이 메모리 블록을 가리켜 스택 프레임이라 한다.

       함수 a가 반환되면 이 스택 프레임은 모두 반환된다."

 

 

 

 

- 스택 포인터(Stack Pointer) 레지스터

 

스택에 데이터를 쌓거나 반환하기 위해서는 현재 어느위치까지 데이터를 저장했는지 기억해야 함

=  (쌓아 올린 스택위치를 기억해야 한다는 뜻)

 

이를위해 CPU 내에는 sp(Stack Pointer)라는 이름의 레지스터가 존재함. 우리가 앞서 디자인한 레지스터에 구조에도 포함.

 

sp레지스터 값은 변수가 하나씩 할당될때마다 증가되며, 증가하면서 다음변수가 할당될 메모리 위치를 가리키게 된다.

반면에 호출된 함수가 종료시, 스택 프레임 단위로 sp레지스터 값을 이동시켜야 한다.

 

변수가 선언되면, 현재 sp가 가리키는 위치에 할당되므로, sp위치를 아래로 이동시키는 것만으로도

이전에 선언된 변수를 반환할 수 있다.( 변수 할당 시 이전에 저장된 값들을 덮어 씌우므로)

때문에 sp가 가리키는 위치를 아래로 이동시키는 방식으로 스택프레임을 반환한다.

 

하지만 여기서 한 가지 문제가 있다.

 

-> 스택에 메모리 할당 시: sp값의 증가분은 계산가능 (할당시, 할당 대상타입에 따라 sp를 이동시키면 되므로)

     예) 4바이트 변수 할당시, 4만큼 증가시키면 됨

 

     호출이 완료된 함수 빠져나오는 시점 : 스택프레임 단위로 sp를 아래로 이동시킬때는 문제가 됨

     왜냐면, 얼마만큼 sp를 이동시켜야 할지 알 수 없기 때문이다.

 

     그래서 이를 위한 특별한 장치나 방법이 추가적으로 필요하며, 프레임 포인터 레지스터가 이 역할을 한다.

 

 

 

- 프레임 포인터(Frame Pointer) 레지스터

 

자, 어떤 방법으로 위의 문제를 해결하면 좋겠는가? (sp의 이동크기를 아는 방법)

 

1) 레지스터 하나 추가로 할당

   - 함수 호출시 0으로 초기화, 변수 선언시마다 그 크기값 증가

      -> 이 방법은 변수 선언 시 마다, 뎃셈연산을 해야하는 단점이 있다. 이는 스택연산에 드는 비용을 상당히 늘리게 된다.

 

자, 우리의 고민거리를 다시 생각해보자.

sp위치를 함수 호출이전으로 돌리면 되는 것 아닌가? 오로지 이것이 목적이면 덧셈연산은 필요치 않다.

그냥 되돌아갈(함수호출 이전의)sp위치만 저장해 놓아도 된다.

 

바로, 이런 역할을 하는 레지스터를 가르켜 fp(frame Pointer)레지스터라고 한다.

 

 

자, 그럼 이제 fp 레지스터의 등장으로 모든 문제가 해결된것일까?

 

아니다. 아직 넘어야 할 산이 있다. 호출이 중첩될 경우 fp는 기존값을 덮어씌우게 되기 때문이다.

 

예) fct1()에서 fct2()를 호출할 경우, fct2()호출 후 돌아올 위치는 알고 있지만, fct1()호출 후 돌아올 위치는

     없어져버렸기(덮어씌워졌기)때문에 결국 sp레지스터가 가르켜야 하는 위치를 찾지 못 할 것이다.

 

 

 

 

- 스택에 저장하자, 프레임 포인터(Frame Pointer)

 

 

자, 이번엔 어떤 방법으로 위의 문제를 해결하면 좋겠는가?

 

해결방법은 의외로 간단하다. 바로 덮어쓰는 문제가 발생하기 전에, fp에 저장된 값을 어딘가에 저장하면 된다.

즉, 함수 호출이 일어날 때마다 fp레지스터에 저장되어 있는 값을 스택에 저장하는 것이다.

sp 레지스터에 저장된 값을 fp 레지스터에 옮기기 전에, fp레지스터에 저장된 값을 스택에 쌓아둔다.)

 

 

void main(void)

{

   int a = 1;

   int b = 2;

   ...

}

 

void fct1(void)

{

   int c = 3;

   int d = 4;

   ...

}

 

void fct2(void)

{

   int e = 5;

   int h = 6;

   ...

}

 

 

1)  fct2()가 호출되기 직전

 

sp = 주소값 20이 들어가 있음.

fp = 주소값 8이 들어가 있음.

 

-------------

 

         / 20번지    <-------------- sp : 20번지

d = 4 / 16번지

c = 3 / 12번지

0번지 / 8번지      <-------------- fp : 8번지

b = 2 / 4번지

a = 1 / 0번지

-------------

 

 

 

2)  fct2()가 호출시 -  fp레지스터에 저장된 값을 현재 sp(20번지)에 저장. 그다음 fp에 sp값 20을 저장한다.  

 

sp = 주소값 32이 들어가 있음.

fp = 주소값 20이 들어가 있음.

 

-------------

         / 32번지  

h = 6  / 28번지

e = 5 /  24번지

8번지 / 20번지   <-------------- sp: 20번지

d = 4 / 16번지

c = 3 / 12번지

0번지 / 8번지      <-------------- fp : 8번지 -> 20번지

b = 2 / 4번지

a = 1 / 0번지

-------------

 

 

 

3)  fct2()가 호출 완료 후, 반환 시 -  fp에 저장된 값을 참조해서 sp레지스터값을 20으로 변경 = fct2() 스택프레임 날리는 효과

                                                 현재 sp가 가르키는 위치에 저장된 값을 fp로 옮긴다. fct1()완료시 sp위치를 8로 가져올수 있음.

sp = 20

fp = 8

 

-------------

         / 32번지   

h = 6  / 28번지

e = 5 /  24번지

8번지 / 20번지    <-------------- sp: 32 -> 20번지

d = 4 / 16번지

c = 3 / 12번지

0번지 / 8번지      <-------------- fp : 8번지 -> 20번지 -> 8번지

b = 2 / 4번지

a = 1 / 0번지

-------------