C++ Heap Corruption | "힙 손상" 가이드

C++ Heap Corruption | "힙 손상" 가이드

이 글의 핵심

C++ Heap Corruption에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

Heap Corruption이란?

힙 메모리 관리 구조가 손상되는 문제

// ❌ 힙 손상 예시
int* arr = new int[10];
arr[10] = 42;  // 범위 초과 (힙 손상)
delete[] arr;

발생 원인

// 1. 버퍼 오버플로우
int* arr = new int[10];
arr[15] = 42;  // 범위 초과

// 2. 이중 delete
int* ptr = new int(10);
delete ptr;
delete ptr;  // 이중 해제

// 3. 잘못된 delete
int* arr = new int[10];
delete arr;  // delete[] 사용해야 함

// 4. Use After Free
int* ptr = new int(10);
delete ptr;
*ptr = 42;  // 해제 후 사용

실전 예시

예시 1: 버퍼 오버플로우

#include <iostream>
#include <cstring>

// ❌ 버퍼 오버플로우
void bufferOverflow() {
    char* buffer = new char[10];
    strcpy(buffer, "This is too long");  // 범위 초과
    delete[] buffer;
}

// ✅ 안전한 복사
void safeBuffer() {
    char* buffer = new char[20];
    strncpy(buffer, "This is safe", 19);
    buffer[19] = '\0';
    delete[] buffer;
}

// ✅ std::string 사용
void useString() {
    std::string str = "This is safe";
}

예시 2: 이중 delete

#include <memory>

// ❌ 이중 delete
void doubleFree() {
    int* ptr = new int(10);
    delete ptr;
    delete ptr;  // 크래시
}

// ✅ nullptr 설정
void safeFree() {
    int* ptr = new int(10);
    delete ptr;
    ptr = nullptr;
    delete ptr;  // 안전 (무시됨)
}

// ✅ 스마트 포인터
void smartPointer() {
    auto ptr = std::make_unique<int>(10);
    // 자동 해제
}

예시 3: 잘못된 delete

// ❌ delete vs delete[] 혼용
void wrongDelete() {
    int* arr = new int[10];
    delete arr;  // 힙 손상
}

void wrongDelete2() {
    int* ptr = new int(10);
    delete[] ptr;  // 힙 손상
}

// ✅ 올바른 delete
void correctDelete() {
    int* arr = new int[10];
    delete[] arr;
    
    int* ptr = new int(10);
    delete ptr;
}

// ✅ 스마트 포인터
void smartPointer() {
    auto arr = std::make_unique<int[]>(10);
    auto ptr = std::make_unique<int>(10);
}

예시 4: Use After Free

#include <iostream>

// ❌ 해제 후 사용
void useAfterFree() {
    int* ptr = new int(10);
    delete ptr;
    std::cout << *ptr << std::endl;  // 위험
}

// ✅ 해제 전 사용
void safeUse() {
    int* ptr = new int(10);
    std::cout << *ptr << std::endl;
    delete ptr;
}

// ✅ 스마트 포인터
void smartPointer() {
    auto ptr = std::make_unique<int>(10);
    std::cout << *ptr << std::endl;
}

힙 손상 탐지

// 1. AddressSanitizer
// g++ -fsanitize=address -g program.cpp

// 2. Valgrind
// valgrind --tool=memcheck ./program

// 3. Windows Debug Heap
#ifdef _DEBUG
#include <crtdbg.h>
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

// 4. 커스텀 가드
class HeapGuard {
public:
    static void* allocate(size_t size) {
        const size_t guardSize = 16;
        void* ptr = malloc(size + guardSize * 2);
        
        // 앞뒤에 가드 패턴
        memset(ptr, 0xAA, guardSize);
        memset((char*)ptr + guardSize + size, 0xBB, guardSize);
        
        return (char*)ptr + guardSize;
    }
    
    static void deallocate(void* ptr) {
        if (!ptr) return;
        
        // 가드 검사
        char* realPtr = (char*)ptr - 16;
        // 검사 로직
        free(realPtr);
    }
};

자주 발생하는 문제

문제 1: 배열 경계 초과

// ❌ 범위 초과
int* arr = new int[10];
for (int i = 0; i <= 10; i++) {  // <= 10은 오류
    arr[i] = i;
}
delete[] arr;

// ✅ 올바른 범위
int* arr = new int[10];
for (int i = 0; i < 10; i++) {
    arr[i] = i;
}
delete[] arr;

// ✅ std::vector
std::vector<int> vec(10);
for (int i = 0; i < vec.size(); i++) {
    vec[i] = i;
}

문제 2: 메모리 재할당

// ❌ realloc 오용
int* arr = new int[10];
arr = (int*)realloc(arr, 20 * sizeof(int));  // new와 혼용 불가
delete[] arr;

// ✅ 새로 할당
int* arr = new int[10];
int* newArr = new int[20];
std::copy(arr, arr + 10, newArr);
delete[] arr;
delete[] newArr;

// ✅ std::vector
std::vector<int> vec(10);
vec.resize(20);

문제 3: 포인터 산술

// ❌ 잘못된 포인터 산술
int* arr = new int[10];
int* ptr = arr + 15;  // 범위 초과
*ptr = 42;
delete[] arr;

// ✅ 범위 검사
int* arr = new int[10];
int index = 5;
if (index < 10) {
    arr[index] = 42;
}
delete[] arr;

문제 4: 구조체 멤버

struct Node {
    int* data;
    Node* next;
};

// ❌ 멤버 해제 누락
void deleteNode(Node* node) {
    delete node;  // data 해제 안됨
}

// ✅ 멤버 먼저 해제
void deleteNode(Node* node) {
    delete[] node->data;
    delete node;
}

// ✅ 소멸자 사용
struct NodeSafe {
    std::unique_ptr<int[]> data;
    std::unique_ptr<NodeSafe> next;
};

방지 방법

// 1. 스마트 포인터
auto ptr = std::make_unique<int>(10);
auto arr = std::make_unique<int[]>(10);

// 2. 컨테이너 사용
std::vector<int> vec(10);
std::string str = "Hello";

// 3. RAII 패턴
class Resource {
    int* data;
public:
    Resource(int size) : data(new int[size]) {}
    ~Resource() { delete[] data; }
    
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
};

// 4. 범위 검사
template<typename T>
class SafeArray {
    std::vector<T> data;
public:
    T& operator {
        if (index >= data.size()) {
            throw std::out_of_range("인덱스 초과");
        }
        return data[index];
    }
};

디버깅 도구

# AddressSanitizer
g++ -fsanitize=address -g program.cpp
./a.out

# Valgrind
valgrind --leak-check=full --show-leak-kinds=all ./program

# Dr. Memory (Windows)
drmemory.exe -- program.exe

# Electric Fence
LD_PRELOAD=libefence.so ./program

FAQ

Q1: Heap Corruption은 언제?

A:

  • 버퍼 오버플로우
  • 이중 delete
  • 잘못된 delete
  • Use After Free

Q2: 탐지 방법은?

A:

  • AddressSanitizer
  • Valgrind
  • Windows Debug Heap

Q3: 방지 방법은?

A:

  • 스마트 포인터
  • 컨테이너
  • RAII 패턴

Q4: 증상은?

A:

  • 크래시
  • 예측 불가능한 동작
  • 메모리 손상

Q5: delete vs delete[]?

A:

  • delete: 단일 객체
  • delete[]: 배열

Q6: Heap Corruption 학습 리소스는?

A:

  • “Effective C++”
  • AddressSanitizer 문서
  • Valgrind 문서

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Use After Free | “해제 후 사용” 가이드
  • C++ Memory Leak | “메모리 누수” 가이드
  • C++ Sanitizers | “새니타이저” 가이드

관련 글

  • C++ Memory Leak |
  • C++ Sanitizers |
  • C++ Use After Free |
  • C++ Valgrind |
  • C++ Algorithm Heap |