꾸준함이 중요한 Lion2me의 기술블로그

메모리 영역에 대해서

12 Jul 2021

메모리 영역에 대해서

갑자기 질문을 받았을 때 당연히 알고 있다는 생각을 했던 이 질문에 100점짜리 답을 내놓지 못했다는 안타까움에 글을 남깁니다.

부족한건 부족한 것, 다시 한번 포스팅을 준비하며 공부를 하도록 하겠습니다.

메모리 영역에 대하여

OS에서는 특정 프로그램을 실행 할 때 프로세스라는 형태로 메모리(RAM)에 적재합니다. 이때 어떠한 형태로 메모리에 적재하는지 먼저 알아보도록 하겠습니다.

코드 영역

위의 형태를 보면 코드 영역의 경우에는 실제로 프로그램에서 사용되는 코드가 적재됩니다.

텍스트와 같은 개념으로 적재되기 때문에 텍스트 영역이라고 부르기도 한다고 합니다.

데이터 영역

일반적으로 전역변수(선언 위치에 따른)를 사용하거나 JAVA의 경우 static 변수를 사용하면 다른 클래스에서도 객체가 아닌 클래스에 접근해서 사용 할 수 있다는 것을 알고 있습니다.

예를 들면 Math 클래스가 있습니다.

import java.lang.Math;

System.out.println(Math.abs(-1));
System.out.println(Math.sqrt(25.0));

우리는 Math라는 클래스를 기반으로 객체를 생성 할 필요없이 곧바로 Math내의 함수를 사용하고 있습니다. 이렇게 사용 할 수 있는 이유가 바로 Math 내에 작성 된 함수들이 static으로 이루어져 있기 때문입니다.

즉 Static 변수를 작성 했을 때 프로세스 내에서 공유를 할 수 있는 공간에 저장이 된다는 뜻이며, 그 공간이 데이터 영역입니다.

힙 영역

왜 그 당시에 이게 생각이 안났는지.. 힙 영역은 동적으로 생성한 변수가 적재되는 공간입니다. 데이터 영역 이후에 힙 영역으로 지정이 되며, 필요한 만큼 할당할 수 있기 때문에 메모리 주소는 낮은 주소에서 높은 주소로 향하는 형태로 저장이 됩니다.

흔히 볼 수 있는 다음과 같은 경우가 힙 영역을 이용한 변수 선언입니다.

//new 연산을 사용하여 정의 할 경우 힙 영역을 사용합니다.
String tmp = new String("안녕하세요 Lion2me 입니다.");

//String의 경우 다음과 같이 사용할 수 있습니다.
String tmp = "이렇게 정의하면 자바는 String Pool을 사용합니다."

//조금 독특한 예제이기 때문에 다른 방식으로는
//Class Car이 정의 되었을 경우 가정해보겠습니다.
Car car = new car(engine,door,...);

C언어의 경우에는 molloc이 있겠습니다. 이런 식으로 객체 생성의 경우에는 Heap영역을 사용합니다.

그러면? 힙 영역에 객체를 생성 할 때 아예 스택을 사용하지 않을까요?

그렇지 않습니다. 객체를 생성할 때 스택의 영역을 조금 사용합니다.

흔히 우리가 참조되어있는 공간의 개념에 대해 조금 이해해 볼 필요가 있습니다. 이 부분은 자바보다 C계열의 언어로 가볍게 알아보겠습니다.

프로그래밍 과외를 하며 자신있게 설명하는 포인터를 통해 설명드리겠습니다.

int* a = (int*)malloc(sizeof(int)*5)
int* b = (int*)malloc(sizeof(int)*5)
// 해석 - int의 크기(4byte)에서 5을 곱한 값 만큼 공간을 할당해서
// 우리가 int형의 크기(4byte)씩 읽도록 표현해주세요
// 위의 과정에서 20byte의 공간을 만들고 4바이트씩 읽을 수 있습니다.
// 즉 int 배열[5] 입니다.

a[0] = 0
a[1] = 1
a[2] = 2
// 할당 되어있는 공간을 우리는 a라는 변수 명을 통해 접근 할 수 있습니다.

다음의 그림을 보면 이해할 수 있습니다. 만약 a와 b라는 변수 명을 통해 접근할 수 있습니다.

그러면 a라는 변수 명을 통해 접근하는 공간을 해제하지 않은 채로 a포인터를 b가 접근하는 공간에 연결하면 어떻게 될까요?

a 공간은 접근할 수 없게 되며 힙 영역에는 공간이 남아있는 채로 유지됩니다.

공간을 동적할당할 경우 Heap영역에 실질적인 데이터가 저장되지만 그 위치에 접근하기 위해 저장하는 주소는 Stack에 저장되는 개념입니다.

동적 할당이라고 하더라도 무한정 공간을 할당할 수는 없습니다. 메모리 영역에서 쌓여있는 스택 영역에 접근할 정도로 많은 공간을 할당하면 Heap Overflow가 발생합니다.

이러한 문제를 방지하기 위해서 GC(가비지 컬렉터)가 동작하며, C계열의 언어는 자체적으로 할당한 공간을 해제해주어야 합니다.

스택 영역

흔히 알고있는 스택 영역의 경우는 지역변수 및 매개변수를 관리하는 영역입니다. 지역변수와 매개변수는 왜 스택에 저장이 될까요?

이 부분은 가볍게 설명하겠습니다. 프로그램이 메모리에 적재 될 때 프로세스의 형태로 저장된다고 말했습니다. 그리고 프로세스 내에는 여러 개의 스레드가 작동하며 프로그램이 실행됩니다. 이 부분에서 프로세스는 코드/데이터/힙 영역은 공통적으로 사용하지만 스택은 스레드마다 할당되어 사용됩니다.

그리고 스레드는 독립적으로 동작하는 작업의 단위이기 때문에 main함수에서 A함수를 동작할 때 A함수에 전달 된 매개변수와 A함수 내에서 사용하는 지역변수를 알아야 하며, 그 내용은 다른 스레드에서 알 필요가 없습니다.

다음 그림은 제가 이해한 스택의 구조를 그린 그림입니다.

조금 주의할 점은 스레드에서 함수가 리턴되어 되돌아오는 주소는 프로세스에서 사용하는 PC가 아닙니다. PCB에서 PC값은 프로세스 실행 중 인터럽트를 통해 다른 프로세스 실행 시 실행위치를 저장한 것이므로 위 함수 리턴주소랑은 다릅니다. 혹시나 헷갈릴 것 같기에..

이렇게 함수가 리턴되는 주소가 있어야 실행을 마친 후 함수에서 선언 된 지역변수와 매개변수를 제거하고 다시 이전의 함수를 실행합니다. 그리고 이미지에서 보이는 것처럼 함수는 실행되는 순서대로 쌓이는 LIFO(Last In First Out)의 형태로 작동합니다.

참고

https://jinshine.github.io/2018/05/17/컴퓨터%20기초/메모리구조/

http://tcpschool.com/c/c_memory_structure