In C++, is it defined behaviour to delete a primitive type such as uint32_t through a char*?

Viewed 231

Does the following have defined behaviour?

uint32_t* p = new uint32_t();

char* p2 = reinterpret_cast<char*>(p);

delete p2;

(Is there a standard quote relevant to this?)

I'm aware alternative options exist, but I'm just curious about this one.

Thanks!

2 Answers

From [expr.delete]/3 in the C++17 standard (though this rule goes back to C++11 and probably to earlier, but I don't have that spec handy):

if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

The dynamic type of the object being pointed at is uint32_t. The type of the pointer is char. These are not the same, nor is char a base class of uint32_t, so behavior is undefined.

There is definitely not an equivalence between delete int_ptr; and delete char_ptr;, based on assembly compiled from some basic C++ code:

//C++ Code
void delete_as_int(int* ptr) {
    delete ptr;
}

void delete_as_char(char* ptr) {
    delete ptr;
}

//Assembly; GCC 8.2 x86-64, no optimizations, c++17 mode
delete_as_int(int*):
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov QWORD PTR [rbp-8], rdi
  mov rax, QWORD PTR [rbp-8]
  mov esi, 4 //Difference!
  mov rdi, rax
  call operator delete(void*, unsigned long)
  nop
  leave
  ret
delete_as_char(char*):
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov QWORD PTR [rbp-8], rdi
  mov rax, QWORD PTR [rbp-8]
  mov esi, 1 //Difference!
  mov rdi, rax
  call operator delete(void*, unsigned long)
  nop
  leave
  ret

Simply put, the assembly to delete an int* calculates a different number of bytes to store in a register than the assembly to delete a char* does.

So just on the face of things, it's clear that the type the compiler recognizes matters. Even if for no other reason than to ensure that the correct amount of memory is deleted. So you can't rely on the compiler to magically deduce the correct behavior for deleteing a pointer that has had its type altered by the reinterpret_cast call: you need to make sure to delete pointers as the type they were created as—or for polymorphic types, ensure that the Deleter is virtual.

Related