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 |