c++ – How does the delete[] operator know how many bytes to free?

Question:

Something I thought about the fact that I do not understand one kind of trivial thing.

int *p = new int[20];
delete[] p;

How does the delete[] operator know that exactly 20 * sizeof(int) bytes need to be freed?

What will happen as a result of executing the following code:

#include <iostream>
using namespace std;
int main()
{
    int *p1 = new int[16];
    int *p2 = p1;
    p1 = new int[20];
    cout << p1 << " --- " << p2 << " --- " << p1 - p2 << endl;
    delete[] p1;
    delete[] p2;
    return 0;
}

and is it correct at all? Console output:

0x3d2b30 --- 0x3d2ae8 --- 18

Those. there are 64 more bits between the allocated memory areas. The last number is always even, and is at least 2 more than the size of the first array.

Another experiment (the above was on Win7/Qt/mingw), now Ubuntu14.04/Qt/g++:

int main()
{
    int *p1 = new int[20];
    int *p2 = p1;
    p1 = new int[20];
    int i;
    for (i=0; i<20; i++) {
        p1[i] = 0xAAAAAAAA;
        p2[i] = 0xFFFFFFFF;
    }
    cout << "Pointers: " << p1 << " --- " << p2 << " --- " << p1 - p2 << endl;
    cout << "shifted pinters: " << p1 - 1 << " --- " << p1 - 2 << endl;
    cout << "values in skipped space: " << hex << *(p1-1) << " --- " << *(p1-2)
                                    << " --- " << *(p1-3) << " --- " << *(p1-4) << endl;
    cout << "values from p2 memory: " << hex << *(p1-5) << " --- " << *(p1-6)
                                    << " --- " << *(p1-7) << " --- " << *(p1-8) << endl;
    delete[] p1;
    delete[] p2;
    return 0;
}

Console output:

Pointers: 0x1ebd070 --- 0x1ebd010 --- 24
shifted pinters: 0x1ebd06c --- 0x1ebd068
values in skipped space: 0 --- 61 --- 0 --- 0
values from p2 memory: ffffffff --- ffffffff --- ffffffff --- ffffffff

Answer:

The "new with square brackets" operator stores information about the number of elements in an array. delete[] retrieves this information and calls the destructors on the elements.

The compiler can generate code for these statements like this:

// Исходный код
struct A {
    A();
    ~A();
};

A* a = new A[10];
delete[] a;

//---------------------------------------------------------------
// Код, который генерирует компилятор (см. примечения ниже)

// A* a = new A[10];
A* a;
{
    // выделяем память
    void* _mem = malloc(sizeof(int) + 10 * sizeof(A));
                     // ^- выделяем дополнительную память для размера массива
    int* _size_ptr = (int*)_mem;
    *_size_ptr = 10; // сохраняем размер
    A* a = (A*)&_size_ptr[1]; // "a" указывает на память за сохраненным размером массива
    for (int i = 0; i != 10; ++i)
        ::operator new(a + i) A; // вызываем конструкторы
}

// delete[] a;
{
    // перемещаем указатель на начало выделенной памяти
    int* _size_ptr = (int*)a - 1;
    for (int i = *_size_ptr - 1; i >= 0; --i)
        a[i].~A(); // вызываем деструкторы
    // удаляем память
    free(_size_ptr);
}

Note1: Actually, void* operator new[](size_t bytes) and void operator delete[](void*) functions are called instead of malloc and free . But since they call something similar to malloc/free, in the context of this question, this can be neglected.

Note2: For exception handling, the compiler will generate a try-catch block for new[] .

Scroll to Top