Scalar `new T` vs array `new T[1]`

If T doesn’t have trivial destructor, then for usual compiler implementations, new T[1] has an overhead compared to new T. The array version will allocate a little bit larger memory area, to store the number of elements, so at delete[], it knows how many destructors must be called.

So, it has an overhead:

  • a little bit larger memory area must be allocated
  • delete[] will be a little bit slower, as it needs a loop to call the destructors, instead calling a simple destructor (here, the difference is the loop overhead)

Check out this program:

#include <cstddef>
#include <iostream>

enum Tag { tag };

char buffer[128];

void *operator new(size_t size, Tag) {
    std::cout<<"single: "<<size<<"\n";
    return buffer;
}
void *operator new[](size_t size, Tag) {
    std::cout<<"array: "<<size<<"\n";
    return buffer;
}

struct A {
    int value;
};

struct B {
    int value;

    ~B() {}
};

int main() {
    new(tag) A;
    new(tag) A[1];
    new(tag) B;
    new(tag) B[1];
}

On my machine, it prints:

single: 4
array: 4
single: 4
array: 12

Because B has a non-trivial destructor, the compiler allocates extra 8 bytes to store the number of elements (because it is 64-bit compilation, it needs 8 extra bytes to do this) for the array version. As A does trivial destructor, the array version of A doesn’t need this extra space.


Note: as Deduplicator comments, there is a slight performance advantage of using the array version, if the destructor is virtual: at delete[], the compiler doesn’t have to call the destructor virtually, because it knows that the type is T. Here’s a simple case to demonstrate this:

struct Foo {
    virtual ~Foo() { }
};

void fn_single(Foo *f) {
    delete f;
}

void fn_array(Foo *f) {
    delete[] f;
}

Clang optimizes this case, but GCC doesn’t: godbolt.

For fn_single, clang emits a nullptr check, then calls the destructor+operator delete function virtually. It must do this way, as f can point to a derived type, which has a non-empty destructor.

For fn_array, clang emits a nullptr check, and then calls straight to operator delete, without calling the destructor, as it is empty. Here, the compiler knows that f actually points to an array of Foo objects, it cannot be a derived type, hence it can omit the calls to empty destructors.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)