C++ 메모리 모델: 코드·데이터·스택·힙 영역 완벽 분석

프로그램의 메모리 구조

C++에서 실행 중인 프로그램은 메모리를 네 개의 주요 영역으로 나누어 사용하며, 각 영역은 저장되는 데이터의 종류와 생명 주기가 다릅니다.

  • 코드 영역: 실행할 기계어 코드가 저장됩니다.
  • 전역 영역: 전역 변수, static 변수, 문자열 상수 등이 위치합니다.
  • 스택 영역: 함수의 매개변수, 지역 변수 등이 자동으로 할당·해제됩니다.
  • 힙 영역: 프로그래머가 직접 할당하고 해제하는 동적 메모리 공간입니다.

코드 영역의 특성

코드 영역은 프로그램이 실행되기 전에 이미 할당되며, CPU가 직접 읽어 실행할 명령어들로 구성됩니다. 공유 특성을 가지므로 동일한 프로그램이 여러 번 실행되더라도 메모리에 코드 사본 하나만 존재할 수 있습니다. 또한 읽기 전용으로 보호되어 잘못된 접근으로 인한 명령어 수정을 방지합니다.

전역 영역의 특성

전역 변수, static으로 선언된 변수, 그리고 const가 붙은 전역 상수는 모두 전역 영역에 저장됩니다. 이 영역의 데이터는 프로그램이 종료될 때까지 유지됩니다. 아래 예제에서 전역 변수와 static 변수의 주소가 서로 가까이 위치하는 반면, 지역 변수는 멀리 떨어진 주소를 갖는 것을 확인할 수 있습니다.

#include <iostream>
using namespace std;

int globalA = 15;
int globalB = 25;
const int GLOBAL_CONST = 100;

int main() {
    int localA = 5;
    int localB = 10;

    static int staticVal = 50;
    static int staticVal2 = 60;

    cout << "지역 변수 localA 주소: " << &localA << endl;
    cout << "지역 변수 localB 주소: " << &localB << endl;

    cout << "전역 변수 globalA 주소: " << &globalA << endl;
    cout << "전역 변수 globalB 주소: " << &globalB << endl;

    cout << "static 변수 staticVal 주소: " << &staticVal << endl;
    cout << "static 변수 staticVal2 주소: " << &staticVal2 << endl;

    cout << "문자열 상수 주소: " << &"Hello C++" << endl;
    cout << "전역 상수 GLOBAL_CONST 주소: " << &GLOBAL_CONST << endl;

    const int localConst = 200;
    cout << "지역 상수 localConst 주소: " << &localConst << endl;

    return 0;
}

실행 결과를 통해 전역/static 변수와 상수는 서로 인접한 주소에 배치되고, 지역 변수는 이들과 현저히 다른 주소 대역에 존재함을 알 수 있습니다.

스택 영역의 동작

스택은 함수 호출 시 자동으로 메모리를 할당하고 함수가 종료되면 자동으로 해제합니다. 따라서 함수 내부에서 생성된 지역 변수의 주소를 반환하면 안 됩니다. 반환된 주소는 이미 해제된 메모리 영역을 가리키므로 미정의 동작을 유발합니다.

#include <iostream>
using namespace std;

int* unsafeFunc() {
    int temp = 7;          // 스택에 할당된 지역 변수
    return &temp;          // 위험: 스택 주소 반환
}

int main() {
    int* ptr = unsafeFunc();
    cout << *ptr << endl;  // 예측 불가능한 값 또는 크래시
    return 0;
}

위 코드는 컴파일러 경고를 발생시키며, 실행 시 이미 해제된 스택 메모리를 참조하게 됩니다.

힙 영역의 활용

힙은 프로그래머가 명시적으로 할당·해제해야 하는 메모리 공간입니다. new 연산자를 사용하여 메모리를 할당하고, 사용이 끝나면 delete로 반환해야 합니다. 포인터를 이용해 힙에 생성된 데이터에 접근하며, 배열의 경우 delete[]로 해제합니다.

#include <iostream>
using namespace std;

int* createHeapInt() {
    int* heapPtr = new int(99);   // 힙에 int 할당
    return heapPtr;
}

void demoArray() {
    int* arr = new int[5]();      // 5개 int 배열, 0으로 초기화
    for (int i = 0; i < 5; ++i) {
        arr[i] = (i + 1) * 3;
    }
    for (int i = 0; i < 5; ++i) {
        cout << "arr[" << i << "] = " << arr[i] << endl;
    }
    delete[] arr;                 // 배열 해제
}

int main() {
    int* val = createHeapInt();
    cout << "힙에 저장된 값: " << *val << endl;
    delete val;                   // 단일 객체 해제
    // cout << *val << endl;   // 해제 후 접근은 위험

    demoArray();
    return 0;
}

힙 메모리는 직접 해제하지 않으면 프로그램 종료 시까지 유지되지만, 메모리 누수 원인이 될 수 있으므로 주의해야 합니다. newdelete를 짝을 맞추어 사용하는 것이 중요합니다.

태그: C++ 메모리모델 스택 new

8월 8일 11:41에 게시됨