Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 6 – References, pointers and memory

Dynamic memory in C++: new, delete and leaks

Create and destroy objects with new and delete, find leaks, double deletes and new[]/delete mismatches with sanitizers, then replace them with standard types.

  • Intermediate
  • 25 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free: 25 minutes

What you will learn

  • Create and destroy objects and arrays with new, delete, new[] and delete[]
  • Recognise use after delete, double deletes, new[]/delete mismatches and leaks in sanitizer reports
  • Find leaks on macOS, Linux and Windows with the tool each platform provides
  • Replace manual memory management with std::vector and std::unique_ptr

Before you start

What you’ll learn · Before you start

What you will learn

  • Create and destroy objects and arrays with new, delete, new[] and delete[]
  • Recognise use after delete, double deletes, new[]/delete mismatches and leaks in sanitizer reports
  • Find leaks on macOS, Linux and Windows with the tool each platform provides
  • Replace manual memory management with std::vector and std::unique_ptr

Before you start

On this page

A new expression creates an object with dynamic storage duration: new int{42} reserves memory on the free store (often called the heap), creates an int there and returns a pointer to it. The object is not tied to any scope; it lives until a delete expression ends it, so delete p; destroys the object and gives its memory back. Arrays have their own pair, new T[n] and delete[] p.

Every new needs exactly one matching delete, and that rule is easy to break. Forget the delete and the memory stays reserved, a leak. Delete twice, or use the object after delete, and the program has undefined behaviour. Release an array with plain delete and it is undefined again. This lesson shows each mistake as two compilers, the sanitizers and macOS’s leaks tool report it, how to find leaks on each platform, and the replacement that modern code uses instead: containers and smart pointers, which make delete disappear from ordinary code.

new and delete

new_delete.cpp C++ · new_delete.cpp
#include <cstddef>
#include <print>
#include <string>

struct Student {
    std::string name;
    int marks;
    Student(std::string n, int m) : name(std::move(n)), marks(m) { std::println("  Student {} created", name); }
    ~Student() { std::println("  Student {} destroyed", name); }
};

int main() {
    int* count = new int{42};                // one int with dynamic storage duration
    std::println("*count = {}", *count);
    delete count;                            // ends its lifetime and gives the memory back

    Student* s = new Student{"Asha", 72};    // new allocates, then runs the constructor
    s->marks += 5;
    std::println("{} has {} marks", s->name, s->marks);
    delete s;                                // delete runs the destructor, then frees

    std::size_t n = 4;
    int* scores = new int[n]();              // n ints, set to 0 by the ()
    scores[n - 1] = 99;
    std::println("scores {} {} {} {}", scores[0], scores[1], scores[2], scores[3]);
    delete[] scores;                         // memory from new[] goes back with delete[]
}
Three objects, created and destroyed by hand Python · new_delete_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address new_delete.cpp -o new_delete && ./new_delete
g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address new_delete.cpp -o new_delete && ./new_delete
""")

Output

$ clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address new_delete.cpp -o new_delete && ./new_delete
*count = 42
  Student Asha created
Asha has 77 marks
  Student Asha destroyed
scores 0 0 0 99
$ g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address new_delete.cpp -o new_delete && ./new_delete
*count = 42
  Student Asha created
Asha has 77 marks
  Student Asha destroyed
scores 0 0 0 99

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 new_delete_session.py

new does two things, in order: it allocates memory, then it runs the constructor, which is why Student Asha created appears at the new line. delete reverses them: it runs the destructor, then frees the memory. new int[n] creates n ints whose count may be known only at run time, and the empty parentheses in new int[n]() set each one to zero; without them the ints would hold no defined value. The sanitizer builds of both compilers ran the program without a report, because every object was deleted exactly once and in the right form.

Pointers on the stack point to objects on the free store; one points to freed memory and dangles, and one block has no pointer: a leak.Stack: local variables of mainFree store: objects made by newcount, an int*s, a Student*old, an int*after delete oldint42StudentAsha, 77freed memoryno object here nowint[100]no pointer refers to it:a leakdangling

Pointers on the stack, objects on the free store

Text description of the diagram

The diagram has two areas side by side.

On the left is the stack, with three local pointer variables of main: count, an int*; s, a Student*; and old, an int* after delete old.

On the right is the free store, with the objects that new created: an int holding 42, a Student holding Asha and 77, a dashed box of freed memory where no object is any more, and a dashed box for an int[100] array that no pointer refers to.

Arrows connect the pointers to their objects: count points to the int 42 and s to the Student. A dashed arrow labelled "dangling" goes from old to the freed memory: the pointer still holds the address, but the object is gone. No arrow reaches the int[100] array: nothing can delete it any more, so it is a leak.

The pointer variables, such as count and s, are ordinary local variables: they live on the stack and disappear at the end of main. The objects they point to live on the free store until delete ends them. Nothing connects the two lifetimes, and that gap is where every bug in this lesson comes from: old in the diagram still holds an address after its object was deleted, and the int[100] block has lost its last pointer.

Mistake 1: using an object after delete

After delete, the pointer still holds the old address, but the object is gone and the standard makes every pointer into that storage invalid ([basic.stc]). Reading through it is undefined behaviour:

use_after_free.cpp C++ · use_after_free.cpp
#include <print>
#include <string>

int main() {
    std::string* name = new std::string{"Ravi"};
    delete name;
    std::println("{}", name->size());        // the string has been destroyed: undefined behaviour
}
Reading a deleted string Python · use_after_free_session.py
from session import run

run("""
g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address use_after_free.cpp -o use_after_free && ./use_after_free
""")

Output

$ g++-16 -std=c++23 -Wall -Wextra -g -fsanitize=address use_after_free.cpp -o use_after_free && ./use_after_free
==…==ERROR: AddressSanitizer: heap-use-after-free on address 0x…
READ of size 8 at 0x… thread T0
    #1 in main use_after_free.cpp:7
0x… is located 8 bytes inside of 32-byte region [0x…,0x…)
freed by thread T0 here:
    #1 in main use_after_free.cpp:6
previously allocated by thread T0 here:
    #1 in main use_after_free.cpp:5
SUMMARY: AddressSanitizer: heap-use-after-free
[42 more lines of the report left out]
/bin/sh: line 1: … Abort trap: 6           ./use_after_free
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 use_after_free_session.py

AddressSanitizer reports a heap-use-after-free and prints three stacks: the bad read on line 7, the delete that freed the memory on line 6 and the new that allocated it on line 5. The read was 8 bytes into a 32-byte block, which is where libstdc++’s std::string keeps its size. A pointer that may outlive its object is a design smell; the quick fix is to set it to nullptr right after delete, and the real fix is to give the object an owner, as the last section shows.

Mistake 2: deleting twice

Passing an invalid pointer to a deallocation function is undefined behaviour too, so deleting the same object twice is not merely wasteful:

double_delete.cpp C++ · double_delete.cpp
#include <print>

int main() {
    int* seats = new int{40};
    std::println("seats {}", *seats);
    delete seats;
    delete seats;                    // the same object a second time: undefined behaviour
    std::println("done");
}
A second delete of the same object Python · double_delete_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra double_delete.cpp -o double_delete && ./double_delete
clang++ -std=c++23 -g -fsanitize=address double_delete.cpp -o double_delete && ./double_delete
""")

Output

$ clang++ -std=c++23 -Wall -Wextra double_delete.cpp -o double_delete && ./double_delete
seats 40
/bin/sh: line 1: … Trace/BPT trap: 5       ./double_delete
[exit status 133]
$ clang++ -std=c++23 -g -fsanitize=address double_delete.cpp -o double_delete && ./double_delete
seats 40
==…==ERROR: AddressSanitizer: attempting double-free on 0x… in thread T0:
    #1 in main double_delete.cpp:7
0x… is located 0 bytes inside of 4-byte region [0x…,0x…)
freed by thread T0 here:
    #1 in main double_delete.cpp:6
previously allocated by thread T0 here:
    #1 in main double_delete.cpp:4
SUMMARY: AddressSanitizer: double-free double_delete.cpp:7 in main
[11 more lines of the report left out]
/bin/sh: line 1: … Abort trap: 6           ./double_delete
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 double_delete_session.py

On this Mac the plain build stopped at the second delete with a trap, exit status 133, before printing done and without a single word about why. AddressSanitizer names the bug, attempting double-free, with the stacks of the second delete on line 7, the first one on line 6 and the allocation on line 4. A double delete usually means that two parts of a program each believe they own the object, a question of ownership rather than of syntax.

Mistake 3: new[] released with delete

An array created with new[] must be released with delete[], and a single object with plain delete: [expr.delete] makes every other combination undefined behaviour, and the C++ Core Guidelines repeat it as rule ES.61. With ints the wrong form often appears to work, which is what makes this mistake persistent:

mismatch.cpp C++ · mismatch.cpp
#include <print>

int main() {
    int* scores = new int[3]{72, 85, 64};
    std::println("first {}", scores[0]);
    delete scores;                   // allocated with new[], so this needs delete[]
    std::println("done");
}
new[] with delete: warnings, then a silent sanitizer Python · mismatch_session.py
from session import run

run("""
clang++ -std=c++23 mismatch.cpp -o mismatch && ./mismatch
g++-16 -std=c++23 -Wall mismatch.cpp -o mismatch && ./mismatch
clang++ -std=c++23 -g -fsanitize=address mismatch.cpp -o mismatch 2>/dev/null && ./mismatch
ASAN_OPTIONS=alloc_dealloc_mismatch=1 ./mismatch
""")

Output

$ clang++ -std=c++23 mismatch.cpp -o mismatch && ./mismatch
mismatch.cpp:6:5: warning: 'delete' applied to a pointer that was allocated with 'new[]'; did you mean 'delete[]'? [-Wmismatched-new-delete]
    6 |     delete scores;                   // allocated with new[], so this needs delete[]
      |     ^
      |           []
mismatch.cpp:4:19: note: allocated with 'new[]' here
    4 |     int* scores = new int[3]{72, 85, 64};
      |                   ^
1 warning generated.
first 72
done
$ g++-16 -std=c++23 -Wall mismatch.cpp -o mismatch && ./mismatch
mismatch.cpp: In function 'int main()':
mismatch.cpp:6:12: warning: 'void operator delete(void*, std::size_t)' called on pointer returned from a mismatched allocation function [-Wmismatched-new-delete]
    6 |     delete scores;                   // allocated with new[], so this needs delete[]
      |            ^~~~~~
mismatch.cpp:4:40: note: returned from 'void* operator new [](std::size_t)'
    4 |     int* scores = new int[3]{72, 85, 64};
      |                                        ^
first 72
done
$ clang++ -std=c++23 -g -fsanitize=address mismatch.cpp -o mismatch 2>/dev/null && ./mismatch
first 72
done
$ ASAN_OPTIONS=alloc_dealloc_mismatch=1 ./mismatch
first 72
==…==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new [] vs operator delete) on 0x…
    #1 in main mismatch.cpp:6
0x… is located 0 bytes inside of 12-byte region [0x…,0x…)
allocated by thread T0 here:
    #1 in main mismatch.cpp:4
SUMMARY: AddressSanitizer: alloc-dealloc-mismatch mismatch.cpp:6 in main
[9 more lines of the report left out]
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 mismatch_session.py

Both compilers warn when they can see the new[] and the delete in one function: Clang by default and GCC with -Wall, both as -Wmismatched-new-delete. Both programs then printed done anyway. The AddressSanitizer build printed done as well, without a report, because on macOS this check is off by default: the sanitizers project documents alloc_dealloc_mismatch as on by default except on Darwin and Windows. With ASAN_OPTIONS=alloc_dealloc_mismatch=1 the same program stops with alloc-dealloc-mismatch (operator new [] vs operator delete). When the allocation happens in another function, as in the exercise below, neither compiler can warn, so turn that option on whenever you test with AddressSanitizer on a Mac.

Mistake 4: forgetting delete, a leak

A leak is not undefined behaviour: the memory simply stays reserved until the program ends. For a short program that is harmless; for a server or a game that runs for days and leaks on every request or frame, memory use grows until the system has to stop it. Here the pointer to the first array is overwritten, so nothing can ever free that array:

leak.cpp C++ · leak.cpp
#include <cstddef>
#include <print>

// Returns a new array of n zeros. The caller must delete[] it.
int* make_counts(std::size_t n) {
    return new int[n]();
}

int main() {
    int* counts = make_counts(100);
    counts[7] = 3;
    std::println("counts[7] = {}", counts[7]);
    counts = make_counts(100);               // the first array is still allocated, and now nothing points to it
    delete[] counts;
}
Looking for the leak on macOS Python · leak_session.py
from session import run

# Leak checks on macOS: AddressSanitizer's leak detector is not supported on this Mac, and the system tool leaks is.
run("""
clang++ -std=c++23 -g -fsanitize=address leak.cpp -o leak && ASAN_OPTIONS=detect_leaks=1 ./leak
clang++ -std=c++23 -g leak.cpp -o leak && leaks -quiet -atExit -- ./leak 2>/dev/null
""")

Output

$ clang++ -std=c++23 -g -fsanitize=address leak.cpp -o leak && ASAN_OPTIONS=detect_leaks=1 ./leak
==…==AddressSanitizer: detect_leaks is not supported on this platform.
/bin/sh: line 1: … Abort trap: 6           ASAN_OPTIONS=detect_leaks=1 ./leak
[exit status 134]
$ clang++ -std=c++23 -g leak.cpp -o leak && leaks -quiet -atExit -- ./leak 2>/dev/null
counts[7] = 3
Process …: 186 nodes malloced for 16 KB
Process …: 1 leak for 448 total leaked bytes.

STACK OF 1 INSTANCE OF 'ROOT LEAK: <malloc in make_counts(unsigned long)>':
5   dyld                                  0x… start + 6992
4   leak                                  0x… main + 28  leak.cpp:10
3   leak                                  0x… make_counts(unsigned long) + 48  leak.cpp:6
2   libc++abi.dylib                       0x… operator new(unsigned long) + 52
1   libsystem_malloc.dylib                0x… _malloc + 96
0   libsystem_malloc.dylib                0x… _malloc_zone_malloc_instrumented_or_legacy + 152
====
    1 (448 bytes) ROOT LEAK: <malloc in make_counts(unsigned long) 0x…> [448]

[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 leak_session.py

Finding leaks depends on the platform:

  • Linux. AddressSanitizer includes LeakSanitizer, and Clang’s documentation says leak detection is on by default on Linux: a program built with -fsanitize=address prints a leak report when it exits. -fsanitize=leak gives the leak check alone.
  • macOS. Clang’s documentation says leak detection can be enabled on macOS with ASAN_OPTIONS=detect_leaks=1, but on this Apple silicon Mac the run stops with detect_leaks is not supported on this platform. The system tool leaks works instead: leaks -atExit -- ./program runs the program, examines its memory when it exits, and prints each block that no pointer refers to any more, with the stack where it was allocated. Here it found one leaked block of 448 bytes, the allocator’s block for the 400-byte array, created in make_counts on line 6 and called from line 10, and it exits with status 1, so a test script can fail on it. man leaks describes the options.
  • Windows. The debug C runtime of Visual Studio tracks allocations in debug builds, and a call to _CrtDumpMemoryLeaks() before the program ends prints “Detected memory leaks!” with each block still allocated. With _CRTDBG_MAP_ALLOC and a debug version of new, as Microsoft’s documentation shows, each block also comes with the file and line that allocated it.

The modern replacement: owners instead of delete

Every mistake above has the same cause: a raw pointer does not know that it is responsible for an object. The C++ Core Guidelines’ rule R.11 says to avoid calling new and delete explicitly, and R.20 says to represent ownership with unique_ptr or shared_ptr. Here are the first program and the leak again, with owners:

rewrite.cpp C++ · rewrite.cpp
#include <cstddef>
#include <memory>
#include <print>
#include <string>
#include <vector>

struct Student {
    std::string name;
    int marks;
    Student(std::string n, int m) : name(std::move(n)), marks(m) { std::println("  Student {} created", name); }
    ~Student() { std::println("  Student {} destroyed", name); }
};

// n zeros, owned by the vector that is returned.
std::vector<int> make_counts(std::size_t n) { return std::vector<int>(n); }

int main() {
    auto count = std::make_unique<int>(42);              // owns one int
    std::println("*count = {}", *count);

    auto s = std::make_unique<Student>("Asha", 72);      // owns one Student
    s->marks += 5;
    std::println("{} has {} marks", s->name, s->marks);

    std::vector<int> scores(4);                          // owns four ints, all 0
    scores.back() = 99;
    std::println("scores {} {} {} {}", scores[0], scores[1], scores[2], scores[3]);

    std::vector<int> counts = make_counts(100);
    counts[7] = 3;
    counts = make_counts(100);                           // the old elements are freed by the assignment
    std::println("counts[7] = {}", counts[7]);
}                                                        // every owner frees what it owns: no delete anywhere
The same work with std::unique_ptr and std::vector Python · rewrite_session.py
from session import run

run("""
clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address rewrite.cpp -o rewrite && ./rewrite
clang++ -std=c++23 -Wall -Wextra -g rewrite.cpp -o rewrite && leaks -quiet -atExit -- ./rewrite 2>/dev/null
""")

Output

$ clang++ -std=c++23 -Wall -Wextra -g -fsanitize=address rewrite.cpp -o rewrite && ./rewrite
*count = 42
  Student Asha created
Asha has 77 marks
scores 0 0 0 99
counts[7] = 0
  Student Asha destroyed
$ clang++ -std=c++23 -Wall -Wextra -g rewrite.cpp -o rewrite && leaks -quiet -atExit -- ./rewrite 2>/dev/null
*count = 42
  Student Asha created
Asha has 77 marks
scores 0 0 0 99
counts[7] = 0
  Student Asha destroyed
Process …: 185 nodes malloced for 15 KB
Process …: 0 leaks for 0 total leaked bytes.

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rewrite_session.py

std::make_unique<Student>("Asha", 72) creates the object and returns a std::unique_ptr<Student> that deletes it when the owner goes out of scope (R.23 prefers make_unique to writing new yourself). std::vector<int> scores(4) replaces new int[4](): it owns its elements, knows its size and frees them itself. Assigning a new vector to counts frees the old elements, so the leak of the previous section cannot happen, and leaks reports 0 leaks for 0 total leaked bytes. Student Asha destroyed now prints at the closing brace of main, where the owner s ends.

Owners also fix what no amount of care fixes by hand: when a function returns early or an exception passes through it, a delete written at the end of the function never runs, while an owner’s destructor runs as the function is left. Classes and RAII, and the lessons on smart pointers later in this track, show how these owners work inside. For now the rule is: in ordinary code, store objects in containers or std::unique_ptr, and let raw pointers only observe.

Your turn: five memory bugs in a linked list

This hand-written linked list has five memory bugs. Fix them so that the program prints 4 3 2 1, 3 2 1, 3 1 and first 3, size 2 on four lines, AddressSanitizer reports nothing with alloc_dealloc_mismatch=1, and the leak check of your platform finds no leaks:

linked_list_exercise.cpp C++ · linked_list_exercise.cpp
// A hand-written singly linked list with five memory bugs. Fix them so that the program prints
//   4 3 2 1 / 3 2 1 / 3 1 / first 3, size 2
// on four lines, AddressSanitizer reports nothing and the leak check finds no leaks.
#include <print>

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

struct List {
    Node* head = nullptr;
};

void push_front(List& list, int value) {
    list.head = new Node{value, list.head};
}

void pop_front(List& list) {
    Node* old = list.head;
    delete old;
    list.head = old->next;
}

// Unlinks the first node that holds value.
void remove(List& list, int value) {
    Node** link = &list.head;
    while (*link != nullptr && (*link)->value != value) link = &(*link)->next;
    if (*link != nullptr) *link = (*link)->next;
}

int size(const List& list) {
    int n = 0;
    for (const Node* p = list.head; p != nullptr; p = p->next) ++n;
    return n;
}

// A new array with the values, front first. The caller frees it.
int* values(const List& list) {
    int* out = new int[size(list)];
    int i = 0;
    for (const Node* p = list.head; p != nullptr; p = p->next) out[i++] = p->value;
    return out;
}

void print(const List& list) {
    for (const Node* p = list.head; p != nullptr; p = p->next) std::print("{} ", p->value);
    std::println("");
}

void clear(List& list) {
    for (Node* p = list.head; p != nullptr; p = p->next) delete p;
}

int main() {
    List list;
    for (int v : {1, 2, 3, 4}) push_front(list, v);
    print(list);
    pop_front(list);
    print(list);
    remove(list, 2);
    print(list);
    int* copy = values(list);
    std::println("first {}, size {}", copy[0], size(list));
    delete copy;
    clear(list);
    clear(list);                     // clearing an empty list again must be harmless
}

As given, neither compiler warns about anything, and the first sanitizer report stops in pop_front:

The exercise as given Python · linked_list_session.py
from session import run

# The exercise as given: the compilers' warnings, then the first report of AddressSanitizer.
run("""
clang++ -std=c++23 -Wall -Wextra -c linked_list_exercise.cpp -o linked_list_exercise.o
g++-16 -std=c++23 -Wall -Wextra -c linked_list_exercise.cpp -o linked_list_exercise.o
clang++ -std=c++23 -g -fsanitize=address linked_list_exercise.cpp -o linked_list_exercise 2>/dev/null
ASAN_OPTIONS=alloc_dealloc_mismatch=1 ./linked_list_exercise
""", max_lines=40)

Output

$ clang++ -std=c++23 -Wall -Wextra -c linked_list_exercise.cpp -o linked_list_exercise.o
$ g++-16 -std=c++23 -Wall -Wextra -c linked_list_exercise.cpp -o linked_list_exercise.o
$ clang++ -std=c++23 -g -fsanitize=address linked_list_exercise.cpp -o linked_list_exercise 2>/dev/null
$ ASAN_OPTIONS=alloc_dealloc_mismatch=1 ./linked_list_exercise
4 3 2 1
==…==ERROR: AddressSanitizer: heap-use-after-free on address 0x…
READ of size 8 at 0x… thread T0
    #0 in pop_front(List&) linked_list_exercise.cpp:22
    #1 in main linked_list_exercise.cpp:59
0x… is located 8 bytes inside of 16-byte region [0x…,0x…)
freed by thread T0 here:
    #1 in pop_front(List&) linked_list_exercise.cpp:21
    #2 in main linked_list_exercise.cpp:59
previously allocated by thread T0 here:
    #1 in push_front(List&, int) linked_list_exercise.cpp:16
    #2 in main linked_list_exercise.cpp:57
SUMMARY: AddressSanitizer: heap-use-after-free linked_list_exercise.cpp:22 in pop_front(List&)
[41 more lines of the report left out]
[exit status 134]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 linked_list_session.py

  1. Fix the bug the report names, build again and run again; each run shows the next bug.
  2. When AddressSanitizer is quiet, run the leak check: leaks -atExit -- ./linked_list_exercise on macOS, the same AddressSanitizer build on Linux.
  3. The bugs are in pop_front, remove, clear (two of them) and main.

First hint: the report’s freed by stack says where the object was deleted, and the READ line says where it was used afterwards. Read the next node’s address before you delete the node that holds it.

When it works, try the modern version: replace Node* next with std::unique_ptr<Node> next, and see which of the five bugs can no longer be written.

Self-check

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 Put the steps of Student* s = new Student{"Asha", 72}; followed later by delete s; in the order they happen.

    Give each item its position, from 1 (first).

    Show the answer to question 1

    Answer:

    1. Memory for a Student is allocated on the free store
    2. The Student constructor runs
    3. The Student destructor runs
    4. The memory is given back

    new allocates, then constructs; delete destroys, then frees. That is why "created" appeared at the new line and "destroyed" at the delete line in the lesson's run.

  2. Question 2 of 5 int* scores = new int[10]; Which statement releases it correctly?

    Choose one answer.

    Show the answer to question 2

    Answer: delete[] scores;

    An array from new[] goes back with delete[]. Plain delete or free() is undefined behaviour, and setting the pointer to nullptr only loses the address, which leaks the array.

  3. Question 3 of 5 On macOS, why did the AddressSanitizer build of mismatch.cpp print "done" without a report?

    Choose one answer.

    Show the answer to question 3

    Answer: Its new[]/delete mismatch check is off by default on macOS; setting ASAN_OPTIONS to alloc_dealloc_mismatch=1 turns it on

    The sanitizers project documents alloc_dealloc_mismatch as on by default except on Darwin and Windows. With the option set, the same build reported alloc-dealloc-mismatch.

  4. Question 4 of 5 Which command found the leak in leak.cpp on the Apple silicon Mac used in this lesson?

    Choose one answer.

    Show the answer to question 4

    Answer: leaks -atExit -- ./leak

    AddressSanitizer stopped with "detect_leaks is not supported on this platform", and a leak never stops a program by itself. The system tool leaks reported one leaked block with the stack where it was allocated.

  5. Question 5 of 5 int* counts = make_counts(100); … counts = make_counts(100); leaks the first array. What change removes the leak without any delete?

    Choose one answer.

    Show the answer to question 5

    Answer: Return and store a std::vector<int>, whose assignment frees the old elements

    A std::vector owns its elements: assigning a new vector to counts frees the old elements, and the vector frees the rest when it goes out of scope, so leaks reported 0 leaks for the rewritten program.

Interview questions

Fresher · What is the difference between delete and delete[]?

delete p ends one object created with new and delete[] p ends an array created with new T[n]; each must be paired with its own form, and any other combination is undefined behaviour. delete[] runs the destructor of every element of the array, while plain delete expects a single object. The mismatch may seem to work with int, which is why tools matter: both GCC and Clang warn with -Wmismatched-new-delete when they see both lines, and AddressSanitizer reports it, on macOS only with ASAN_OPTIONS=alloc_dealloc_mismatch=1. In modern code the question rarely comes up, because std::vector and std::unique_ptr<T[]> release arrays correctly by themselves.

Key takeaways

  • new allocates and constructs; delete destroys and frees. Every new needs exactly one matching delete, and new[] needs delete[].
  • Using an object after delete and deleting it twice are undefined behaviour; AddressSanitizer reports both with the stacks of the bad access, the delete and the new.
  • On macOS, AddressSanitizer checks new[]/delete mismatches only with ASAN_OPTIONS=alloc_dealloc_mismatch=1, and its leak detector is not supported on Apple silicon; use leaks -atExit -- ./program.
  • On Linux, -fsanitize=address reports leaks at exit; on Windows, the CRT debug heap’s _CrtDumpMemoryLeaks() does.
  • Modern code lets owners free memory: std::vector for arrays, std::make_unique for single objects, and raw pointers only to observe.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.