필자가 혼자서 인터넷에 올라온 문서들로 공부한 것임으로 내용의 정확도가 떨어질 수도 있다. 그러니 틀린 내용은 꼭! 지적해주기 바란다.
우리가 컴퓨터 프로그램을 실행할 때 우리눈에는 보이지 않는 많은 일들이 일어나게 된다. 특히 C 강의를 진행하면서 메모리 관련 용어들이 많이 나올것이다. 처음 내가 공부를 시작했던 컴파일러 언어는 C#이다. 현재는 C부터 다시 공부를 시작하면서 쌓아온 배경지식 덕분에 C#을 혼자 공부해도 충분히 어려움이 없었지만 처음 공부할 당시만 해도 변수와 메모리 관계, 스택, 힙이란 단어는 너무 생소하며 이해라기 보다는 암기에 가까웠고 이런 상황에 인스턴스라는 개념은 다른 먼 나라, 새로운 차원의 안드로메다 개념 이야기와 같았다. 이런 메모리 이야기가 나올 때마다 무의식 적으로 그 중요도를 외면 했었다. 메모리와 프로그램은 굉장히 밀접한 관계이다. 이 강의를 모두 읽고 나서는 이 말이 당연한 말이지만 그만큼 중요함을 먼저 알리기 위해 일부로 넣었다. 그럼 이제 본격적으로 메모리에 대해서 공부를 해보자.
메모리는 어디에 있는 것일까??
우리가 컴퓨터 내부구조를 봤을 때 흔히 램이라고 일컫는 부품이 있다. 램...그렇다. 그 녀석이 메모리다. 우리는 데이터를 저장하는 메모리 종류가 크게 스택과 힙이 있다고 알고 있다. 그렇다면 램은 물리적으로 스택과 힙 영역이 나눠져 있는 것일까? (처음 공부할 땐 이게 정말 궁금했었다) 답은 '아니다'이다. 램에는 물리적으로 영역이라는 개념이 전혀 없다고 봐도 된다. 그렇다면 메모리 영역은 언제 나눠지게 되는 것일까? 이것은 프로그램이 실행 될 때 메모리 안에서 무슨 일이 일어나는지 알아보면서 같이 소개하겠다.
프로그램이 실행 될 때 메모리 안에서는 무슨일이?
프로그램은 실행 될 때 소스 코드부터 프로그램이 사용할 모든 데이터가 메모리에 올라가 동작하게 된다. 즉 메모리, 램이 없으면 프로그램이 실행 될 수 없는 것이다. 그럼 먼저 메모리가 올라가기 전에 메모리 구조에 대해서 알아보자.
위 그림은 프로그램이 실행되지 않았을 때 메모리 상태를 그린 것이다. 물론 OS도 프로그램이기 때문에 메모리에 올라가지만 이 부분은 생략하였다.
그림을 보면 스택과 힙 같은 영역은 전혀 보이지 않는다. 그림이 보여주 듯 메모리는 프로그램이 실행되지 않으면 단지 텅빈 공간과 같다. 메모리 위치는 주소로 나타내며 그림에서 통상적으로 아래쪽이 낮은 주소를 갖게 되고 높은 곳이 높은 주소를 갖게 된다. 이 주소로 우리가 원하는 데이터를 갖고 오게 된다. 주소라는 개념은 포인터를 공부할 때 중요함으로 기억해두자. 이제 프로그램을 실행하여 메모리에 어떻게 위치하는지 그리고 스택과 힙은 어떻게 생기는지 알아보자.
프로세스 즉 프로그램이 실행이 되면 세그먼트 단위로 메모리에 올라가게 된다. 위 그림에서 나타내는 세그먼트는 하나의 프로그램을 나타내며 각각의 세그먼트는 서로 다른 프로그램, 프로세스를 뜻하게 된다.
세그먼트 크기는 어떻게 정해지는 것일까?
우리가 컴퓨터를 구입할 때 32비, 64비트 이야기를 들었을 것이다. 이 비트는 CPU가 한번에 연산 할 수 있는 데이터 크기를 뜻한다. 메모리 이야기를 하다가 갑자기 CPU이야기를 하는지 의아해 할 것이다. CPU 연산 크기에 따라 세그먼트 크기가 달라지기 때문이다. 즉 32bit CPU는 0 ~ 2^32 - 1 크기의 세그먼트 범위를 갖으며 64bit CPU는 0 ~ 2^64 - 1 범위를 갖을 수 있다. CPU는 세그먼트 크기와 함께 컴퓨터에 장착되는 램 용량 인식 최대 범위가 달라지게 된다. 32비트 CPU에는 최대 4GB까지만
인식이 되며 64비트는 16TB까지 인식 된다.
잠시 생각해보자 뭔가 이상하지 않은가? 32비트 CPU 환경에서는 램이 최대 4GB까지 인식 된다고 하였다. 그런데 프로그램 하나당 최대 4GB 크기의 세그먼트 크기를 제공한다고 하였다. 요즘 같은 멀티테스킹 환경 컴퓨터 안에서는 적어도 프로그램 2개 이상 실행을 한다. 그렇다면 메모리는 4GB 이상 필요하지 않을까? 그래야 프로그램이 동작하지 않을까? 이것은 우리가 흔히 알고 있는 가상메모리라는 기술로 해결한다고 한다. 그리고 모든 프로그램이 무조건 4GB 크기로 세그먼트 크기가 정해지는 건 아니다 다음 세그먼트 구조를 나눠보면 알겠지만 코드 크기와 프로세스가 사용할 총 데이터 크기를 계산하여 세그먼트 크기가 결정이 된다.
32비트 계산
세그먼트 구조
하나의 세그먼트는 다시 크게 세개 세그먼트로 나눠지게 된다.
1번 세그먼트 - Text Segment 또는 Code Segment, 기계어 코드들이 올라오게 된다. 이 세그먼트에는
기타 상수들이 저장되며 쓰기가 허용되지만 크기는 고정되어있다.
스택 세그먼트와 힙 세그먼트
스택 세그먼트와 힙 세그먼트를 설명해주기 전에 스택과 힙에 대한 개념을 먼저 집고 넘어가겠다.
스택을 소개할 때 흔히 FILO(First In Last Out) 또는 LIFO(Last In First Out)라고 한다. 값이 먼저 들어 간(할당된) 데이터가 나중에 나오는(호출이 되는) 메모리 구조가 바로 스택이다.
힙은 스택과 다르게 FIFO(First In First Out) 또는 LILO(Last In Last Out)라 칭한다. 말 그대로 먼저 들어간 데이터가 가장 먼저 나오며 마지막으로 들어간 데이터가 마지막으로 나온다는 말이다.
스택과 힙에 대한 개념은 다음 강의 때 지역변수와 더불어 다시 설명하겠다.
그림에서 화살표는 각 메모리 크기가 증가하는 방향을 나타낸 것이다. 스택과 힙의 크기는 동적으로 변하며 스택과 힙은 마주보는 방향으로 증가 감소 한다. 스택이 증가하면 힙이 줄어드는 형식이다.
힙 메모리 주소는 메모리 크기가 증가하는 방향으로 메모리 주소도 증가하게 된다. 즉 실제 메모리 주소 크기가 증가하는 방향으로 힙 메모리도 주소값이 증가하게 된다. 그러나 스택은 반대이다. 스택 메모리가 증가하는 방향으로 메모리 주소값이 증가하지만 실제 메모리 주소랑은 다르다. 스택 메모리가 증가하는 방향은 실제 메모리 주소가 낮아지는 방향이므로 스택 메모리의 높은 주소의 위치는 실제 메모리 낮은 주소가 된다. 어렵게 풀어 놓은 말은 아래 그림으로 한번에 이해가 될것이다.
이것으로 프로세스가 실행 될 때 메모리 구조를 살펴보았다. 이 개념들은 C 포인터에서도 중요하지만 C++, 자바, C# 그외 언어에도 중요한 내용이므로 이 강의 이외에 다른 문서들을 참고하여 확실히 학습해 놓자.
참고문서
Jon Erickson, 에이콘, 해킹 공격의 예술
달고나, wowhacker, 해커 지망자들이 알아야 할 BufferOverflow Attack의 기초