[2026] C++ Memory Management: new/delete, Stack vs Heap, and RAII
이 글의 핵심
Deep dive into C++ memory: stack vs heap, dynamic allocation with new/delete, RAII, smart pointers, and common pitfalls—new, delete[], placement new, and leak patterns.
Stack vs heap
Stack memory
다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
void func() {
int x = 10; // stack
int arr[100]; // stack
} // automatic cleanup
Characteristics:
- Fast
- Limited size (often ~1–8 MB per thread)
- Automatic lifetime
Heap memory
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
void func() {
int* ptr = new int(10); // heap
// ....use ...
delete ptr; // manual release
}
Characteristics:
- Slower allocation
- Much larger practical limit
- Manual management (or smart pointers)
Dynamic allocation (new/delete)
Single object
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Allocate
int* ptr = new int; // default-initialized (indeterminate for int)
int* ptr2 = new int(10); // value-initialize to 10
int* ptr3 = new int{10}; // C++11 braced init
// Free
delete ptr;
delete ptr2;
delete ptr3;
Arrays
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// Allocate
int* arr = new int[100];
// Use
arr[0] = 1;
arr[99] = 100;
// Free
delete[] arr; // must use [] for arrays!
Class objects
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Person {
public:
string name;
Person(string n) : name(n) {
cout << name << " constructed" << endl;
}
~Person() {
cout << name << " destroyed" << endl;
}
};
int main() {
Person* p = new Person("Alice");
p->name = "Bob";
delete p; // destructor runs
}
RAII (Resource Acquisition Is Initialization)
Basic idea
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class FileHandler {
private:
FILE* file;
public:
FileHandler(const char* filename) {
file = fopen(filename, "w");
if (!file) {
throw runtime_error("failed to open file");
}
cout << "file opened" << endl;
}
~FileHandler() {
if (file) {
fclose(file);
cout << "file closed" << endl;
}
}
void write(const char* data) {
fprintf(file, "%s\n", data);
}
};
int main() {
try {
FileHandler fh("output.txt");
fh.write("Hello");
// Even if an exception is thrown, destructor closes the file
} catch (exception& e) {
cerr << e.what() << endl;
}
}
Practical examples
Example 1: Avoiding leaks
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <memory>
using namespace std;
// ❌ Leak risk
void badExample() {
int* data = new int[1000];
if (someCondition) {
return; // no delete — leak!
}
delete[] data;
}
// ✅ RAII-safe
void goodExample() {
unique_ptr<int[]> data = make_unique<int[]>(1000);
if (someCondition) {
return; // automatic cleanup
}
// automatic cleanup at scope end
}
int main() {
goodExample();
return 0;
}
Note: Smart pointers ensure memory is released on exceptions and early returns.
Example 2: Custom memory pool
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
class MemoryPool {
private:
vector<T*> pool;
size_t nextIndex;
public:
MemoryPool(size_t size) : nextIndex(0) {
pool.reserve(size);
for (size_t i = 0; i < size; i++) {
pool.push_back(new T());
}
cout << "pre-allocated " << size << " objects" << endl;
}
~MemoryPool() {
for (T* obj : pool) {
delete obj;
}
cout << "memory pool destroyed" << endl;
}
T* acquire() {
if (nextIndex < pool.size()) {
return pool[nextIndex++];
}
return nullptr;
}
void reset() {
nextIndex = 0;
}
};
int main() {
MemoryPool<int> pool(100);
int* p1 = pool.acquire();
int* p2 = pool.acquire();
*p1 = 10;
*p2 = 20;
pool.reset(); // reuse
return 0;
}
Note: Pools reduce repeated alloc/free cost in hot paths.
Example 3: placement new
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <new>
using namespace std;
class Object {
public:
int value;
Object(int v) : value(v) {
cout << "Object(" << value << ") constructed" << endl;
}
~Object() {
cout << "Object(" << value << ") destroyed" << endl;
}
};
int main() {
// Pre-allocated buffer
char buffer[sizeof(Object) * 3];
// Construct in place
Object* obj1 = new (&buffer[0]) Object(1);
Object* obj2 = new (&buffer[sizeof(Object)]) Object(2);
cout << obj1->value << ", " << obj2->value << endl;
// Explicit destructor calls
obj1->~Object();
obj2->~Object();
// buffer is stack memory — no delete on buffer
return 0;
}
Note: placement new constructs an object in existing storage; you must call destructors manually.
Common problems
Problem 1: Double delete
Symptom: Crash Cause: Deleting the same pointer twice Fix: 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ double delete
int* ptr = new int(10);
delete ptr;
delete ptr; // crash!
// ✅ set to nullptr after delete
int* ptr = new int(10);
delete ptr;
ptr = nullptr;
delete ptr; // safe (delete on nullptr is a no-op)
Problem 2: delete vs delete[]
Symptom: Leak or heap corruption
Cause: Using delete on array allocation
Fix:
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ wrong
int* arr = new int[10];
delete arr; // wrong — only first element “freed” incorrectly
// ✅ correct
int* arr = new int[10];
delete[] arr;
// ✅ single object
int* ptr = new int(10);
delete ptr;
Problem 3: Dangling pointer
Symptom: Use-after-free Cause: Using pointer after delete Fix: 다음은 cpp를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ dangling
int* ptr = new int(10);
delete ptr;
cout << *ptr << endl; // undefined!
// ✅ nullptr check
int* ptr = new int(10);
delete ptr;
ptr = nullptr;
if (ptr) {
cout << *ptr << endl;
} else {
cout << "pointer is nullptr" << endl;
}
FAQ
Q1: When stack vs heap?
A:
- Stack: small, short-lived data
- Heap: large buffers, dynamic size, or long lifetime
Q2: What if new fails?
A: bad_alloc is thrown.
아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
try {
int* huge = new int[1000000000000];
} catch (bad_alloc& e) {
cout << "allocation failed: " << e.what() << endl;
}
Q3: malloc vs new?
A:
- malloc: C API, no constructors
- new: C++ style, constructors, type-safe
Q4: Why RAII?
A: Exception safety and no manual resource leaks—core to modern C++.
Q5: How do I find leaks?
A:
- Valgrind (Linux)
- Visual Studio memory profiler
- AddressSanitizer (
-fsanitize=address)
Q6: Should I always use smart pointers?
A: Prefer them whenever you express ownership. Raw pointers for non-owning observers, C APIs, or legacy interop.
Related reading (internal links)
Practical tips
Debugging
- Check compiler warnings first.
- Reproduce with a minimal test.
Performance
- Don’t optimize without profiling.
- Set measurable goals.
Code review
- Anticipate common review comments.
- Follow team conventions.
Practical checklist
Before coding
- Is this the right technique?
- Can the team maintain it?
- Does it meet performance needs?
While coding
- Warnings cleared?
- Edge cases handled?
- Error handling in place?
At review
- Intent clear?
- Tests enough?
- Docs adequate?