개요
안녕하세요 피터입니다.
오늘은 응용프로그램의 메모리 구조에 대해 설명드리겠습니다. 메모리 구조에 대해 이해하고 있어야 여러분이 만든 프로그램이 컴퓨터에서 어떻게 동작하는지 이해할 수 있습니다.
이러한 컴퓨터 구조에 대한 이해가 궁극적으로는 더 효율적이고 안정적인 프로그램을 만들 수 있는 통찰력이 됩니다.
여러분이 작성한 코드는 컴파일 과정을 거쳐 실행 가능한 코드(File)이 됩니다.
실행파일을 실행하면 OS에서 해당 실행파일을 주기억장치(RAM)으로 적재(Load)를 합니다. 이렇게 적재된 실행코드들(Instruction)이 순차적으로 CPU 레지스터(Register)에 옮겨져 실행되는 것입니다.
그러면 메모리에 적재된 여러분의 프로그램이 어떤 구조를 갖게 되는지 살펴보겠습니다.
위 그림 처럼 프로그램이 메모리 상에 적재되는 구조는 크게 Stack, Heap, Data, Code(text) 이렇게 4개의 영역으로 나눌 수 있습니다.
1. Stack 영역
특정 구문 내에서 임시로 할당하는 메모리 영역으로 ‘{ }’와 같은 브레이스(braces : 중괄호)내에서 유효한 데이터가 저장됩니다. 프로그램 코드상에서 ‘{‘ 를 만나면 새로운 스택 영역이 시작되고 ‘}’를 만나면 생성했던 데이터들이 해제됩니다.
여려분이 작성한 프로그램의 함수에서 다른 함수를 콜(Call)한 경우 해당 함수의 { } 에 대응해서 스택 영역이 생성되고 해제되겠죠.
이 때 함수를 호출할 때 사용된 매개변수도 마찬가지로 스택에 저장되기 때문에 함수가 종료되면 해제되어버립니다. (이를 방지하려면 Call by reference 방식으로 구현해야 합니다)
스택 영역은 그림처럼 상위에서부터 아래로 증가하면서 할당이 되는데요 중요한 것은 컴파일 시에 크기가 결정된다는 것입니다.
일반적으로 컴파일러 기본 설정은 1MByte로 제한되는데요. 이게 무슨 얘기냐 하면은 여러분의 프로그램이 스택 영역을 1MByte 이상 할당하려고 하면 ‘Stack Overflow’ (같은 이름의 유명한 사이트 이름이 있죠) 에러를 보시게 될 겁니다.
실제로 한번 해보세요.
메인 함수에 아래와 같은 변수 선언 코드를 입력해봅니다.
double isThisPossible[100000000];
이렇게 하고 컴파일해보면 컴파일은 잘 되지만 실행하게 되면 친숙한 Stack Overflow 에러 메시지를 보시게 됩니다.
이런 경우 외에도 재귀함수(Recursive Function)를 남용하다 보면 마찬가지로 Stack Overflow를 마주치실 수 있습니다. (함수의 끝인 ‘}’ 를 만나야 지역변수들이 해제가 되는데 계속해서 재귀적으로 호출하다 보면 언젠가 스택 영역이 바닥나겠죠?)
스택 사이즈는 컴파일러 환경 설정에서 변경이 가능하지만 되도록이면 변경하지 않는 것을 추천드립니다.
덩치가 큰 메모리는 Heap 영역에 할당해서 사용하면 되기 때문입니다.
2. Heap 영역
프로그램에서 자유롭게 할당하고 해제할 수 있는 영역입니다.
여러분이 힙 영역에 메모리를 할당하거나 해제하려면 명시적으로 함수를 호출해야 합니다.
여기서 할당 가능한 크기는 OS 환경에 따라 차이가 있을 수 있는데요. 32bit 운영체제에서는 일반적으로 약 2GByte 의 힙 영역을 할당할 수 있습니다.
이러한 제한이 생기는 이유는 32bit (4Byte)의 메모리 주소 체계에서 약 4GBbyte의 메모리를 표현할 수 있는데 OS, 드라이버 등이 고정적으로 사용하는 영역이 있기 때문에 실질적으로 응용 프로그램에서 사용 가능한 영역은 2GByte 정도입니다.
64bit 운영체제는 이론상 한계치가 있긴 하지만 사실상 제한이 없다고 봐도 되겠습니다.
이처럼 힙 영역은 스택 영역에 비해 굉장히 큰 사이즈 또는 많은 양의 데이터도 적재가 가능한데요. 이를 활용해 대량의 데이터는 힙 영역에 할당하고 데이터를 가리키는 포인터 변수를 스택영역에 지역변수로 할당해서 사용하는 것이 일반적입니다.
하지만 이 힙 영역에 메모리를 할당하고 해제하는 것을 잊어버린다면 메모리 누수(Memory Leak)라는 크나큰 재앙을 불러올 수 있습니다.
메모리 누수 문제는 여러분의 프로그램이 실행되는 동안 시스템의 메모리가 계속해서 잠식해나가 결국 시스템에 치명적인 문제를 일으킬 수 있습니다!
따라서 malloc, calloc, new 등의 할당 함수를 통해 힙 영역을 할당 받았다면 반드시 사용 후에 free, delete 등의 함수로 해제를 해 줘야 합니다.
바로 이러한 메모리 관리야말로 C / C++ 프로그래머에게 가장 중요한 덕목입니다.
Java, C# 와 같은 고수준의 언어의 경우는 Garbage Collector(GC) 라는 녀석이 주인 잃은 메모리(?) - 더 이상 참조되지 않는 메모리 영역 - 을 자동으로 찾아서 해제해주기 때문에 이런 부분에 크게 신경쓰지 않아도 되지만 C나 C++는 그렇지 않습니다. 반드시 할당한 메모리는 해제해야 합니다. 명심하세요!
C++ 에도 스마트 포인터(auto_ptr)라는 것을 사용하면 참조되지 않는 메모리를 자동으로 해제되게 할 수 있습니다. 이 부분은 나중에 자세하게 설명드리겠습니다.
3. Data 영역
위 그림에서 보시다시피 bss, data 영역을 묶어서 data 영역이라고 합니다.
이 영역에는 전역변수, 정적(static)변수, 배열, 구조체 등이 저장되는데 프로그램이 실행될 때 할당 되고 종료될 때 해제됩니다.
이 때 초기화 된 데이터는 data영역에 저장되고 초기화 되지 않은 데이터는 BSS(Block Stated Symbol) 영역에 저장됩니다.
4. Code(text) 영역
코드영역에는 실제 프로그램 동작을 수행하는 명령어(Instruction)와 전역 상수가 저장됩니다.
명령어는 CPU에서 순차적으로 실행되는 함수와 연산 구문 등이 해당되고, 전역 상수는 “ “ 로 선언된 문자열 상수나 const 접두어가 붙은 변수 등이 해당됩니다.
이 영역은 Read only로 변경되지 않습니다.
- Peter의 우아한 프로그래밍
여러분의 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.