ARM Cortex에 대해 공부하던 중, Stack, Heap과 같은 메모리구조에 대해 정리해놓을 필요가 있을 것 같아
이번 포스트에서는 메모리구조에 대해 공부한 내용을 정리 해보려고 한다.

- 메모리 영역

메모리는 대표적으로 4가지 영역으로 분류가 가능하다.
4가지 영역은 Code, Data, Stack, Heap

메모리 영역

1. Code(Text)
실행할 프로그램의 코드가 저장되는 영역이다.
다시말해 실행할 프로그램의 기계어 코드가 담겨있는 영역으로 CPU는 해당 영역에 저장된 명령을 하나씩 가져가 처리하게 된다.


2. Data
우리가 작성한 코드에서 전역변수, 정적변수 등이 할당되는 공간이다.
전역변수 같은 경우, main함수 이전에 선언되므로 main함수가 호출되기 이전에 Data영역에 할당되고
해당 변수들은 프로그램이 끝날 때까지 메모리에 남아있다.

Data영역은 추가적으로 두가지로 구분할 수가 있는데
바로 초기화된 변수 영역 .data(initialized data segment)과 초기화되지 않은 변수 영역 .bss(Block Started by Symbol)이다.

int x = 10;
int y;

int main()
{
...
}

말 그대로 전역변수가 초기값이 있느냐, 없느냐에 따라 구별되는 것으로
위와 같은 코드에서 10으로 초기화된 전역변수 x의 경우 .data영역에,
초기화되지 않은 전역변수 y의 경우 .bss영역에 할당되겠다.


3. Stack
Stack 영역은 함수 호출 시 생성되는 지역변수, 매개변수가 저장되는 영역으로
프로그램이 자동으로 사용하는 임시 메모리 영역이다.
Stack 영역에 데이터가 들어가고 나오는 방식에 대해서는 PUSH, POP 방식 그리고 선입후출(LIFO)로 설명할 수가 있다.

int main()
{
  ...
  func1();
  ...
}

int func1()
{
  ...
  func2();
  ...
}

int func2()
{
  ...
}

위와 같이 main에서 func1을 부르고 func1에서 func2를 부르는 코드가 있다고 할 때,
Stack에 데이터가 어떻게 쌓이는지 살펴보자.

Stack PUSH, POP 그리고 LIFO

참고로 PUSH, POP은 데이터가 Stack에 저장될 때 PUSH, Stack에 저장된 데이터를 빼낼 때 POP이라고 표현한다.
또한 main -> func1 -> func2로 PUSH가 되었을 때, POP될 때는 늦게 쌓인 func2 -> func1 -> main 순으로 되는 것을 확인할 수 있듯이 먼저 들어온(선입) 데이터가 나중에 빠지는(후출) 선입후출(LIFO)방식이다.


4. Heap
Heap 영역은 Stack 영역과 비슷하지만 사용자에 의해 관리되는 임시 메모리 영역이다.
즉, 사용자가 필요에 의해 동적으로 메모리를 할당할 때 사용되는 영역이다.

이상 메모리의 4가지 영역을 살펴봤는데 Stack과 Heap 개념의 차이점을 명확히 알기에는 위의 내용으로는 부족해
다른 분의 블로그를 참고해 공부한 내용을 아래에 따로 정리해보았다 :)


- Stack vs Heap

Stack 영역의 경우 프로그램에 의해 자동으로 관리되는 정적할당영역이며
Heap 영역의 경우 필요에 따라 사용자에 의해 관리되는 동적할당영역이라고 소개했다.
즉, Stack과 Heap의 차이는 할당이 정적이냐, 동적이냐이고
정적, 동적의 의미는 각 영역이 할당되는 시점이다.

정적할당 되는 Stack 영역의 경우, 설계자가 Code를 짜고 Compile을 할 때(Compile Time) 그 크기가 결정이 된다.
하지만 동적할당 되는 Heap 영역의 경우, Compile할 때가 아닌 이후 실제 프로그램이 동작할 때(Run Time) 크기가 결정이 된다.

Stack과 Heap을 구별하는 가장 큰 차이를 알았으니 그럼 아래의 예시 코드를 보며 실제로 어떻게 되는지 살펴보도록 하자.

int main()
{
  ...
  int arr[10];
  ...
}

위와 같이 크기 10짜리 array를 선언한 코드에서,
크기 10짜리 array는 해당 코드를 컴파일 할 때(Compile Time) Stack 영역에 할당이 될 것이다.
array만 따진다면 int형(4byte)으로 선언된 크기 10짜리 array이므로 총 40byte 공간이 할당이 될 것이다.

그럼 아래와 같은 경우에는 어떻게 될까?

int main() 
{
  ...
  int i = 10;
  int arr[i];
  ...
}

앞서 봤듯이 위 코드에서 선언된 지역변수 i는 해당 코드를 컴파일 할 때(Compile Time) Stack 영역에 할당이 될 것이다.
여기서 중요한 것은 해당 변수가 10으로 초기화되었다는 것은 컴파일 시점(Compile Time)이 아닌 동작 시점(Run Time)에 알 수 있다.
즉, 컴파일 시점에 int로 선언된 변수 i의 크기가 4byte임은 알 수 있지만 해당 변수가 어떤 값을 가지는지는 알 수가 없다.
또 i의 초기값을 컴파일 시점에 알 수가 없으니 크기 i로 선언된 array의 크기 또한 컴파일 시점에는 알 수가 없다.

따라서 이런 상황에서는 설계자는 malloc등을 이용하여 메모리 영역(Heap 영역) 동적 할당을 지정해주어야만 한다.
(컴파일러에 따라 해당 코드는 오류가 발생할 수도 혹은 컴파일러가 자동적으로 할당을 해줄 수도 있다.)

malloc으로 Heap영역에 동적할당을 해준다면 다음과 같다.

int main() 
{
  ...
  int *arr ;
  arr = (int*) malloc (sizeof(int)*10);

  free();
  ...
}

malloc을 통해 int형의 size(4byte) 그리고 array의 크기(10)을 곱한 40byte를 Heap영역에 할당해준 모습이다.
정리하자면 컴파일 시점(Compile Time)에 크기를 알 수 없어 Stack영역에 할당할 수가 없으니
설계자가 malloc과 같은 코드를 통해 동작 시점(Run Time)에 Heap 영역에 할당될 수 있도록 하는 것이다.


참고로 이렇게 malloc을 이용해 Heap 영역 동적할당을 할 때 조심할 부분은 free를 잘 사용해 주는 것이다.

메모리 단편화(Memory Fragmentation)

만약 free를 제때 사용하지 않는다면 위와 같이 빈틈이 군데군데 있는 상태로 데이터들이 쌓이게 되고
결국 Heap 영역을 효율적으로 사용하지 못해 메모리 단편화(Memory Fragmentation)가 발생할 수 있기 때문이다.


메모리 영역에 대해 정리한 내용은 이상으로 끝이다.
이번에 공부한 내용들을 잘 기억하고 있다가 각 영역을 실제로 확인해볼 기회가 있다면 직접 보며 확인해보고 싶다 :)

출처 : https://dsnight.tistory.com/50

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기