Objects on the heap: new and delete

Introduction

malloc only gives you bytes. C++'s new does more: it allocates memory and runs the constructor, and gives back a typed pointer, so no cast is needed. delete runs the destructor and then frees the memory.

Local objects die at the end of their block. Sometimes an object must outlive the function that made it, or the number of objects is only known while the program runs. That is what the heap is for, and in C++ it is reached with new and delete.

How it works

new and delete

#include <iostream>
#include <string>
using namespace std;

class Student {
public:
    string name;
    double cgpa;
    Student(string n, double c) : name(n), cgpa(c) {}
    ~Student() {
        cout << "goodbye " << name << endl;
    }
};

int main() {
    Student* s = new Student("Tanvir", 3.75);
    cout << s->name << " " << s->cgpa << endl;
    s->cgpa = 3.8;
    delete s;
    int n = 3;
    int* marks = new int[n];
    for (int i = 0; i < n; i++) marks[i] = 70 + i * 10;
    cout << marks[2] << endl;
    delete[] marks;
    return 0;
}
Open in compiler
  • new Student("Tanvir", 3.75) builds a Student on the heap and returns its address. Watch the heap: the object appears with its members.
  • s->name reaches a member through a pointer, as in C.
  • delete s; runs ~Student() (it prints "goodbye") and frees the block. After that, s holds an old address: do not use it.
  • new int[n] makes an array whose size is only known at run time. It must be released with delete[], with the brackets. StepVioz stops you if you mix them up (delete on a new[] block), which compiled C++ does not always do.
Made withRelease with
new Tdelete p
new T[n]delete[] p
mallocfree(p)

Syntax

Student* s = new Student("Tanvir", 3.75);   // one object
delete s;

int* marks = new int[n];                    // an array
delete[] marks;

Example

#include <iostream>
using namespace std;

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

int main() {
    Node* head = nullptr;
    for (int i = 1; i <= 3; i++) {
        Node* n = new Node{i, head};
        head = n;
    }
    head = nullptr;   // the list is lost: a memory leak
    cout << "done" << endl;
    return 0;
}
Open in compiler

Each new Node{...} adds a node to the front of a list. Setting head = nullptr loses the only pointer to the first node, so nothing points to any of them: the visualizer marks the blocks as a memory leak. A program that leaks inside a loop slowly uses up all memory.

nullptr is C++'s null pointer. Prefer it to NULL or 0.

In real C++ code you rarely write delete by hand: classes such as vector and string, and smart pointers, own their memory and release it in their destructors. You need new and delete to understand how they work, and for linked structures of your own.

Line by line

  1. Node holds a value and a pointer to the next node, and head starts as nullptr, an empty list.
  2. Each pass of the loop creates a node with new Node{i, head}, which points it at the old front of the list.
  3. head = n makes the new node the front, so after the loop the list is 3, 2, 1.
  4. head = nullptr throws away the only pointer to the list. No delete was called, so all three nodes are leaked.
  5. The program still prints done, because a leak causes no error message. It just wastes memory.

Expected result

done

In the real world

Long-running programs such as servers, browsers and games can slowly fill memory if they leak a little on every request or frame. Tools such as Valgrind and AddressSanitizer exist to find leaks and use-after-free bugs in C++ programs.

Common mistakes

  • Using a pointer after delete (use after free), or deleting it twice.
  • delete on an array from new[], or free on memory from new.
  • Losing the last pointer to a block: a leak.

Best practices

  • Pair every new with a delete in the same class, usually the destructor.
  • Set a pointer to nullptr after deleting it when it stays in use.
  • Prefer vector, string and smart pointers, which free memory for you.

Summary

new allocates heap memory and runs the constructor, and delete runs the destructor and frees it, with delete[] for arrays. Losing the last pointer to a block leaks it, and using a block after delete is a serious bug.

Quick check

1. What does new Student("A", 3.5) do that malloc does not?
2. int *a = new int[10]; How should a be released?
3. What runs when you delete a pointer to an object?

Key takeaways

  • new allocates and constructs, and delete destroys and frees.
  • Memory from new[] must be released with delete[].
  • Every new needs exactly one matching delete.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.