2013년 8월 8일 목요일

프로그램의 메모리 구조

프로그램의 메모리 구조에 대해 간단히 언급하려 한다.
사용자가 컴퓨터에서 어떤 프로그램을 실행하게 되면 그 프로그램이 사용할 컴퓨터의 메모리(RAM)을 운영체제에서 배정받는다.
운영체제마다 프로그램에게 메모리를 달리 배정할 수 있으나 일반적으로 다음과 같다.
예를 들어 myprogram 이라는 프로그램을 실행했다고 가정해 보자.

myprogram 실행 메모리 구조

0x10000000 (하위 주소)
| ...
| Code(.text)
|-------------------------------------
| Data (.data)
|-------------------------------------
| Data (.bss)
|-------------------------------------
| Heap
|-------------------------------------
| Stack
|-------------------------------------
| 환경변수, 명령창 데이터 저장 영역
0x1FFFFFFF (상위 주소)

위의 메모리주소 (0x10000000, 0x1FFFFFFF)는 예를 들어 기술한 것이다.

간단히 살펴보면...
(1)
Code (.text) 영역 (영역을 세그먼트라고도 한다.) 에는 실제로 이 프로그램을 수행하는 기계어와 전역 상수가 들어있다. 기계어는 함수 와 연산 구문 등이 해당되며, 전역 상수는 C언어를 예로 들면 전역으로 설정된 const 타입 variable 등이다. 이 영역의 정보는 read only로서 절대 변경되지 않아야 한다.

(2)
Data (.data) 는 초기화가 된 전역 변수 혹은 정적 변수를 저장하는 영역이며,
Data (.bss) 는 초기화가 되지 않은 전역변수 혹은 정적 변수를 저장하는 영역이다.
bss는 Block Started Symbol의 약자이다.

프로그램이 시작하자마자 Code 영역과 Data 영역은 곧바로 메모리에 탑재된다.

(3)
Heap 영역은 프로그램이 자유롭게 할당하고 해제할 수 있는 영역이다. Heap 영역에서 메모리를 할당받고 사용하려면 명시적으로 메모리를 할당하는 구문(C 에서 malloc 같은 함수)을 수행해야 하며, 그리고 사용을 마쳤으면 역시 메모리를 명시적으로 해제해야 한다. (C 에서 free 함수)

(4)
Stack 영역은 특정 구문 내에서 임시로 할당되는 변수를 저장하는 영역이다.
구문이 시작되고 변수가 선언되면 Stack 영역에 할당되었다가 구문이 종료되면 자동적으로 변수는 메모리에서 해제된다. 우리가 자주 사용하는 지역변수와 함수의 인자(입력 파라미터) 등이 Stack에 할당되어 저장된다.

Heap 과 Stack은 프로그램 시작과 동시에 정보가 들어가는 것이 아니라, 프로그램 수행 중에 사용하게 되는 영역이다.
보통 Heap의 경우는 하위 주소에서 상위 주소쪽으로 변수가 채워지며, Stack의 경우는 상위 주소에서 하위 주소쪽으로 변수가 채워진다. 이는 프로그램 도중 Stack이 얼마나 필요할지 알 수 없으므로 Stack은 거꾸로 채워나간다.

요즈음의 프로그래밍 언어는 Heap과 Stack을 구분지어 이해할 필요가 없어도 되는 경우가 많지만, C언어를 다룬다면 적어도 포인터 등을 배우는 시점에서는 차이를 이해하고 있어야 한다.

다음의 예제들로 갈음한다.

#include <stdio.h>

int j=0;
int k;
int l=1;
const int m=2;

int main()
{
 int a;
 static int b=3;
 static int c;
 const int d=4;
 int e;
 int f=5;

 printf("       int j[%010p] init\n",&j);
 printf("       int k[%010p] uninit\n",&k);
 printf("       int l[%010p] init\n",&l);
 printf("const  int m[%010p] init\n",&m);
 putchar('\n');
 printf("       int a[%010p] uninit\n",&a);
 printf("static int b[%010p] init\n",&b);
 printf("static int c[%010p] uninit\n",&c);
 printf("const  int d[%010p] init\n",&d);
 printf("       int e[%010p] uninit\n", &e);
 printf("       int f[%010p] init\n", &f);

 return 0;
}

결과

       int j[0x0804a028] init
       int k[0x0804a030] uninit
       int l[0x0804a018] init
const  int m[0x080485f0] init

       int a[0xbfa00e70] uninit
static int b[0x0804a01c] init
static int c[0x0804a02c] uninit
const  int d[0xbfa00e74] init
       int e[0xbfa00e78] uninit
       int f[0xbfa00e7c] init

위의 결과를 보면 좀 특이한 사항을 발견할 수 있다.
우선 data 영역은 0x0804a018 ~ 0x0804a01c 근방 정도로 보여지고
bss 영역은 0x0804a028 ~ 0x0804a030 근방 정도로 보여진다.

차근차근 살펴보면
전역 변수인 j, k, l, m 중 코드 상에서 초기화가 된 전역변수는 j, l, m 이다.
여기서 m 은 const 키워드가 들어가서 전역 상수가 되었으므로 맨 하위 주소에 위치해 있는 Code(.text) 영역에 들어간 것으로 보여진다.

여기까지는 이해가 되었으나 나머지 초기화된 변수 j, l 중 j 가 data 영역이 아닌 bss 영역에 들어가 있는 것이 의아하다. 하지만 여기엔 이유가 있다.

C 언어에서 모든 전역변수는 별도로 초기화되지 않으면 모두 0으로 초기화된다. 따라서 결국 이 말을 바꿔 하면 0으로 초기화된 전역변수는 bss영역에, 0이 아닌 값으로 초기화된 전역변수는 data영역에 들어간다는 뜻이 된다.

j 의 경우 코드 상에서 초기화는 되었지만 0으로 초기화되었기 때문에 bss에 들어가는 전역변수 (k) 와 다를 바가 없다. 따라서 j는 bss 영역에 들어가게 된다.

전역 변수 외에도 정적 변수도 data, bss 영역에 들어간다. 따라서 (0이 아닌 값으로 초기화된) 변수 b 는 data 영역에, 초기화되지 않은 c는 bss 영역에 들어간다.

그 이외의 로컬 변수 a, d, e, f 는 모두 stack 영역에 들어간다. 로컬 변수는 const 키워드가 들어간다 하더라도 scope가 종료되면 삭제되어야 하므로 Code(.text) 영역에 들어가지 않는다.

그런데 stack 영역에는 상위주소에서 하위주소쪽으로 변수가 채워진다고 했는데 위의 예제에서는 그런것 같지 않아보인다. 그 이유는 로컬 변수가 코드 상으로는 a, d, e, f 의 순서로 선언이 되었지만 실제로는 같은 scope 내에서 동시에 생성되었다가 동시에 사라지므로 a, d, e, f를 한꺼번에 할당을 했기 때문이다. 그러나 scope이 달라지게 되면 분명히 하위주소쪽으로 변수가 채워진다.

다음 예를 보자.

#include <stdio.h>

int global_var;
int global_initialized_var = 5;

void function()
{
    int stack_var; // main() 에도 같은 이름의 변수가 있음.

    printf("function의 stack_var는 주소 0x%08x에 있다.\n", &stack_var);
}

int main()
{
    int stack_var; // function() 에도 같은 이름의 변수가 있음.
    static int static_initialized_var = 5;
    static int static_var;
    int *heap_var_ptr;

    heap_var_ptr = (int *)malloc(4);

    // 이 변수들은 data 세그먼트에 있다.
    printf("global_initialized_var는 주소 0x%08x에 있다.\n", &global_initialized_var);
    printf("static_initialized_var는 주소 0x%08x에 있다.\n", &static_initialized_var);

    // 이 변수들은 bss 세그먼트에 있다.
    printf("static_var는 주소 0x%08x에 있다.\n", &static_var);
    printf("global_var는 주소 0x%08x에 있다.\n\n", &global_var);

    // 이 변수들은 heap 세그먼트에 있다.
    printf("heap_var는 주소 0x%08x에 있다.\n\n", heap_var_ptr);

    // 이 변수들은 stack 세그먼트에 있다.
    printf("stack_var는 주소 0x%08x에 있다.\n", &stack_var);
    function();
}

이 결과는 아래와 같다.

global_initialized_var는 주소 0x0804a018에 있다.
static_initialized_var는 주소 0x0804a01c에 있다.
static_var는 주소 0x0804a028에 있다.
global_var는 주소 0x0804a02c에 있다.

heap_var는 주소 0x0963c008에 있다.

stack_var는 주소 0xbfe469b8에 있다.
function의 stack_var는 주소 0xbfe4698c에 있다.

위의 결과에서 stack_var 의 부분을 보면 main의 stack_var 보다 function의 stack_var 이 분명히 나중에 stack에 할당이 되므로 function의 stack_var 이 상위주소에 위치해 있다. 즉 stack 영역에서는 하위주소에서 상위주소 쪽으로 할당이 되어진다는 것을 확인할 수 있다.

이정도로 메모리 영역에 대한 설명을 마치고자 한다.

댓글 4개:

  1. 일반 stack_var의 주소는 469b8이고 func의 stack_var는 4698c인데 fucn의 stack_var가 더 하위에 있는거 아닌가요?

    답글삭제
    답글
    1. 아 죄송 이해했습니다. 원래라면 주소값이 더 낮아져야하는게 맞는데 다른 지역변수들은 프로그램이 한꺼번에 할당을 해서 낮은 주소에서 높은 주소로 올라간거군여

      삭제
    2. 네. 그렇습니다. 긁 읽어주셔서 감사합니다.

      삭제