C++ Memory Management: new/delete, Stack vs Heap, and RAII

C++ Memory Management: new/delete, Stack vs Heap, and RAII

이 글의 핵심

Practical guide to C++ memory: stack/heap, new/delete, RAII, and patterns used every day in production code.

Stack vs heap

Stack memory

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

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

// 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

// Allocate
int* arr = new int[100];

// Use
arr[0] = 1;
arr[99] = 100;

// Free
delete[] arr;  // must use [] for arrays!

Class objects

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

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

#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

#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

#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:

// ❌ 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:

// ❌ 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:

// ❌ 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.

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.


  • Memory leaks in practice
  • Stack vs heap basics
  • Stack vs heap deep dive

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?

C++, memory management, new, delete, RAII, heap, stack, smart pointers


  • C++ series
  • Adapter pattern
  • ADL
  • Aggregate initialization