메모리 구조
프로세스의 메모리 (RAM) 구조는 다음과 같은 형태로 구성된다.
코드 영역
메모리의 코드 영역은 실행할 프로그램의 코드(명령어) 자체가 저장되는 영역이다.
텍스트 영역이라고도 불리는데, 이 영역에서 CPU는 저장된 명령어를 하나씩 가져가서 처리한다.
데이터 영역
메모리의 데이터(data) 영역은 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역이다.
데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸한다.
ex) 다음 변수들은 데이터 영역에 저장된다.
int global_value = 10;
void temp()
{
static int static_value = 10;
}
스택 영역
메모리의 스택(stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역이다.
스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸한다.
이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다.
스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당된다.
ex) 다음 코드를 통해 스택 영역에 어떻게 메모리가 잡히는지 보자.
void function(int a, int b)
{
string c;
}
int main()
{
int a = 10;
float b = 10.0f;
string c = "10";
function(0, 0);
return 0;
}
스택 프레임
..
// main 함수 호출
------------------------main-----------------------
// 매개변수
x
// 지역 변수
1. 할당됨 int a (4-byte)
2. 할당됨 float b (4-byte)
3. 할당됨 string c (n-byte)
// funtion 함수 호출
-----------------------function----------------------
// 매개변수
4. 할당됨 int a (4-byte)
5. 할당됨 int b (4-byte)
// 지역 변수
6. 할당됨 string c (n-byte)
..
다음과 같은 순서로 스택 프레임에 메모리가 할당될 것이다.
스택의 경우 메모리 해제는 할당된 순서의 역순으로 해제된다. 예를들어 위 함수가 끝나고 블록을 나가는 순간 다음과 같이 해제될 것이다.
..
// main 함수 호출
------------------------main-----------------------
// 매개변수
x
// 지역 변수
7. 할당 해제 int a (4-byte)
6. 할당 해제 float b (4-byte)
5. 할당 해제 string c (n-byte)
4. 할당됨 return int (4-byte), 할당해제
// funtion 함수 호출
-----------------------function----------------------
// 매개변수
3. 할당 해제 int a (4-byte)
2. 할당 해제 int b (4-byte)
// 지역 변수
1. 할당 해제 string c (n-byte)
..
스택에서의 메모리 할당과 해제는 자명하다. 스택프레임에서의 현재 위치에서부터 순서대로 할당되고, 해제 또한 현재 스택 위치에서부터 일어난다. 그렇기 때문에 메모리 할당과 해제에 있어서 복잡한 알고리즘이 필요하지 않다. 스택의 꼭대기에 있는 메모리만 가지고 놀면 된다. (힙의 경우는 다름)
힙 영역
메모리의 힙(heap) 영역은 운영체제에 의해 메모리 공간이 동적으로 할당되고 해제되는 영역이다.
힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당된다.
ex) 힙 영역에 int 크기(4-byte) 만큼 할당된다.
void temp()
{
// 동적할당
int* p = new int;
}
힙 영역에 메모리가 할당될 때에는 스택영역과 달리 여러가지 예외상황이 발생할 수 있다. 스택에서는 메모리가 할당된 순서대로 해제되기 때문에 발생하지 않던 문제인데, 힙에서는 먼저 할당된 메모리가 먼저 해제될 수 있다. 그렇기 때문에 메모리 할당 상태가 다음처럼 흩어져 있을 수 있다.
....
할당됨
할당됨
....
....
....
....
할당됨
할당됨
....
....
....
....
그럼 이처럼 흩어진 메모리에서 꽤 큰 사이즈의 메모리를 할당시켜주어야 하는 상황에는 어떻게 해야할까? 운영체제에서는 흩어진 메모리들을 최대한 효율적으로 수집하여 요청받은 메모리 할당을 처리한다. 이러한 메모리 할당 과정에서는 꽤나 복잡한 알고리즘이 필요한데, malloc과 같은 할당자들에 구현되어있다.
실제 환경
실제 운영체제 위에서는 여러 프로세스들이 동작하기 때문에 다음과 같이 구성될 것이다.
운영체제는 프로세스 단위로 RAM으로부터 메모리를 구분하여 할당하고, 프로세스들 내부에서는 다시 각각 코드영역, 데이터영역, 힙 영역, 스택 영역으로 분리된다.
기타
힙 영역이 필요한 이유
한 번쯤 의문을 가져볼만한 질문이 있다. 운영체제를 설계하는 과정에서 왜 메모리 영역을 스택 영역과 힙 영역으로 분리해서 설계했을까? 이에 대해 한번 생각해보자.
만약 메모리 구조를 설계할 때 스택은 없고, 힙만 존재한다고 가정해보자.
힙의 메모리 할당에는 위에서 언급한 내용대로 복잡한 알고리즘을 통해 할당과 해제를 진행한다. 그러므로 함수호출과 로컬변수의 사용처럼 메모리 할당 및 해제가 자명한 상황에 있어서는 힙을 이용하는것은 비효율적이다. 스택은 이와 같은 상황에 있어서 최고의 자료구조이기 때문에, 효율적인 메모리 구조 설계를 위해 스택을 채용하는것은 필연적인 일이었을 것이다.
반대로 힙이 없고 스택만 존재하는 메모리 구조를 생각해보자.
이 경우에는 성능적인 문제보다 사용성 측면의 문제가 발생한다. 다음과 같은 상황을 생각해보자.
먼저 변수 a를 스택에 할당하고, 다음으로 변수 b를 할당한다. 그 이후 a를 강제로 해제하려한다.
변수 a 메모리에 할당
변수 b 메모리에 할당
변수 a 메모리에서 해제 (?)
메모리 구조에 스택밖에 없다면 위 변수들도 스택에 할당될 것이고 그 구조는 다음과 같다.
스택 프레임
-------
..
변수 b <- 스택 프레임에서 현재 인덱스 위치
변수 a
..
-------
스택에서는 b를 내버려둔 상태로 a를 제거할 수 없다.
그러므로 위와 같은 상황에 대처하기 위해 힙 영역 또한 존재할 수 밖에 없다.
만약 위 상황에서 a를 억지로 제거한다고 하면 어떻게 될까? 그 경우 더 이상 그것을 스택이라 부를 수 없을 것이고, 위에 힙 영역에 대해 설명했었던 메모리가 흩어지는 문제가 똑같이 발생하게 된다. 결국 스택 영역이 힙 영역이 되어버린다.
힙 영역은 Heap으로 설계되어 있는가?
결론부터 말하면 아니다.
과거에 메모리 영역에서의 힙 영역은 자료구조 힙으로 설계되어 있다고 생각했던 적이 있었다. 그러고 나니 문득 궁금했던 것은, 어떻게 자료구조 힙이 흩어진 메모리를 규합해서 할당하는 최고의 알고리즘을 위해 사용될 수 있는지였다. 나중에 알게 되었는데, 메모리 힙 영역은 자료구조 힙으로 구현되어있지 않고 복잡한 여러가지의 자료구조와 알고리즘으로 구현되어 있다는 것이다.
https://stackoverflow.com/questions/1699057/why-are-two-different-concepts-both-called-heap
참고
www.cs.colostate.edu/~fsieker/misc/runtimeStack/runtimeStack.html
'프로그래밍 > CS 개념' 카테고리의 다른 글
[CS 기초] Managed vs Unmanaged 언어 (0) | 2021.04.16 |
---|---|
[CS 기초] 정적타입 언어 vs 동적타입 언어 (0) | 2021.04.16 |