Teivaz
Teivaz

Reputation: 5665

Mixing operator and expression new/delete

Consider following code:

unsigned char* a = new unsigned char[100];
void* a1 = reinterpret_cast<void*>(a);
operator delete[](a1);

short* b = new short[100];
void* b1 = reinterpret_cast<void*>(b);
operator delete[](b1);

It seems to me to be a valid syntax for scalar types as delete-expression is equivalent to calling destructor and operator delete. Same for a POD type:

struct POD {
    int i;
    float f;
    char c;
};
POD* c = new POD[100];
void* c1 = reinterpret_cast<void*>(c);
operator delete[](c1);

Another thing with scalar and POD types allocated using operator new and deallocated with delete expression:

void* d = operator new[](sizeof(unsigned char) * 100);
unsigned char* d1 = reinterpret_cast<unsigned char*>(d);
delete[] d1;

void* e = operator new[](sizeof(short) * 100);
short* e1 = reinterpret_cast<short*>(e);
delete[] e1;

void* f = operator new[](sizeof(POD) * 100);
POD* f1 = reinterpret_cast<POD*>(f);
delete[] f1;

All these examples seem to be well-formed, but I didn't manage to find if it really is. Can someone confirm it or tell me where I am wrong? Especially I am concerned by the fact that all these types have different alignment and it should be passed to operator new/delete:

alignof(unsigned char) 1
alignof(short) 2
alignof(POD) 4


UPD. Some seem to confuse this question with Is it safe to delete a void pointer?. I am asking about mixing expression new with operator delete and operator new with expression delete. According to standard expression forms are implemented with operator forms. And the question is how valid would be mixing them for scalar and POD types?

Upvotes: 2

Views: 210

Answers (3)

Some seem to confuse this question with Is it safe to delete a void pointer?.

(^-- The answer to which is "no, but it might appear to work incidentally...in some cases".)

I am asking about mixing expression new with operator delete and operator new with expression delete.

...which if it were legal, would probably make a good answer to the above question, wouldn't it?

"Nope, you can't use ordinary `delete[]`...but you can use `operator delete[]`.
Its type signature takes a `void*` instead of a typed pointer."

But that's not the answer (and if it were it would make the compiler seem kind of stubborn to not just do that for you.) Either way, the problem is that given just a void pointer and no type information, a compiler does not necessarily have the right "tear-down" code for whatever "build-up" it did specific to the type it was given in new.

(And if you don't think it might be interesting to do something special or distinct for basic types, what if you had an address-sanitizer or undefined-behavior-sanitizer type tool which wanted to throw in something special for short that would be measured differently from another type...maybe because you specifically asked to instrument shorts differently?)

Since ordinary delete is implemented in terms of operator delete[], there's no added magic powers...there's less!

Hence the answer is pretty much the same.

Upvotes: 0

mrtnhfmnn
mrtnhfmnn

Reputation: 407

I think pairing new-expressions and delete-expressions with calls to (even the associated) allocation functions and deallocation functions—like operator delete[]() etc—results in undefined behavior. The former may do some additional housekeeping, while the latter operate on "raw memory", as far as I understand.

This is from ISO/IEC 14882:2014, clause 5.3.5, and it seems to state this explicitly (set in bold by myself):

In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined. In the second alternative (delete array), the value of the operand of delete may be a null pointer value or a pointer value that resulted from a previous array new-expression.81 If not, the behavior is undefined. [ Note: this means that the syntax of the delete-expression must match the type of the object allocated by new, not the syntax of the new-expression. — end note ]

Upvotes: 2

It's not valid at all. When you allocate an array of objects with a destructor, the run time has to remember how many objects to call the destructor on. The easiest way to do that, is to allocate a bit of extra memory before the array, and store the count there. That means if you allocate 3 one-byte objects with a destructor, operator new is going to be asked for (say) 11 bytes of memory - three of which hold the objects, and 8 hold the count (in a size_t).

operator delete wants the address of that 11 byte block of memory - not the address of the 3 byte block that holds/held the three objects.

Now, I know you are asking about built-in types, which don't have a destructor - the point is, that the run-time library may well choose to allocate the count anyway for simplicity.

Upvotes: 3

Related Questions