0

I came across an article on new / operator new:

The many faces of operator new in C++

I couldn't understand the following example:

int main(int argc, const char* argv[])
{
    char mem[sizeof(int)];
    int* iptr2 = new (mem) int;

    delete iptr2;       // Whoops, segmentation fault!

    return 0;
}

Here, the memory for int wasn't allocated using new, hence the segfault for delete.

What exactly does delete not like here? Is there some additional structure hidden when an object is initialized with new, that delete looks for?

EDIT: I'll take out of comments several responses which helped me to understand the situation better:

  1. As @463035818_is_not_a_number and @HolyBlackCat pointed out, mem was allocated on the stack, while delete tries to free memory on the heap. It's a pretty clear cut error and should lead to a segfault on most architectures.

  2. If mem was allocated on the heap without an appropriate new:

The only way to do it I know would be, say, to allocate an int* on a heap, then reinterpret_cast it to an array of chars and give delete a char pointer. On a couple of architectures I tried this on, it actually works, but leads to a memory leak. In general, the C++ standard makes no guarantees in this case, because doing so would make binding assumption on the underlying architecture.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
LazyCat
  • 496
  • 4
  • 14
  • "Here the memory for int wasn't allocated using new, hence the segfault for delete." there isnt much more to it than that. `new (mem) int;` is placement new, which is different – 463035818_is_not_an_ai Jan 21 '23 at 15:38
  • 1
    Only `delete` what you `new`, and `delete[]` what you `new[]`. Placement new is not included in that. – Some programmer dude Jan 21 '23 at 15:38
  • 2
    Its explained in the article. Did you read it? "Calling placement new directly skips the first step of object allocation. We don't ask for memory from the OS. Rather, we tell it where there's memory to construct the object in [3]. The following code sample should clarify this:" nothing is being allocated, so why would you want to `delete` it? – 463035818_is_not_an_ai Jan 21 '23 at 15:39
  • I get it as a rule, but why does it work this way? After all we're just deallocating 4 bytes of memory – LazyCat Jan 21 '23 at 15:39
  • @463035818_is_not_a_number Definitely read the article - I don't see the explanation beyond the comment above – LazyCat Jan 21 '23 at 15:40
  • 1
    the memory is not dynamically allocated. `mem` is an array on the stack. You cannot `delete` it – 463035818_is_not_an_ai Jan 21 '23 at 15:40
  • 1
    If you try to call `delete` on something that wasn't allocated with `new` then you invoke *undefined behavior* - that is the rule defined by the language – UnholySheep Jan 21 '23 at 15:41
  • please do not edit answers into the question. SO is not a discussion forum. If you want to summarize answers/comments you can post an answer. – 463035818_is_not_an_ai Jan 21 '23 at 17:09

4 Answers4

4

delete deletes objects from the heap. Your object is on the stack, not on the heap.

new (new T, not the placement-new you used) does two things: allocates heap memory (similar to malloc()), then performs initialization (for classes, calls a constructor).

Placement-new (what you used) performs initialization in existing memory, it doesn't allocate its own.

And delete does two things: calls the destructor (for class types), then frees the heap memory (similar to free()).

Since your object is not on the heap, delete can't delete its memory.

There's no "placement-delete" that only calls the destructor. Instead, we have manual destructor calls:

If you had a class type, you'd do iptr2->MyClass::~MyClass(); to call the destructor. And freeing the memory is then unnecessary since stack memory is automatically deallocated when leaving the current scope.


Also note that you forgot alignas(int) on your char array.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • Thanks - agree on the stack/heap inconsistency. Do you know what would happen on a typical architecture if it was a pointer to a memory block allocated on a heap? (see my EDIT to the original question) – LazyCat Jan 21 '23 at 16:55
  • @LazyCat You mean `new`, then delete a `reinterpret_cast`ed pointer? Practically, that would call the wrong destructor (if any), because `delete` knows what destructor to call from the pointer type. (This smells of crashes and leaks if the destructor cleans up memory or does something useful.) And formally, this is UB. There might be other issues, when inheritance and virtual destructors are involved. – HolyBlackCat Jan 21 '23 at 17:46
2

What exactly delete doesn't like here?

The fact that it was called for a pointer that did not come from a real new.

Is there some additional structure hidden when an object is initialized with new, that it looks for?

That is a near certainty for your C++ implementation, but it's completely immaterial. This is undefined behavior, full stop. delete is defined only for pointers to objects that were created with a non-placement new operator. Otherwise this is undefined behavior. This is an important distinction. The "your C++ implementation" part is relevant. It's certainly possible that a different C++ compiler or operating system will produce code that doesn't crash, and does nothing at all. Or it may draw a funny face on your monitor screen. Or play a tune that you hate, on your speakers. This is what "undefined behavior" means. And in this case, "undefined behavior" means a crash, in your case.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Thanks. Maybe, it's not clear from my post, but I'm looking for the logic behind this rule. "It is the case, because it's the rule" is not entirely satisfactory even though that's what I tell my kids from time to time.. – LazyCat Jan 21 '23 at 15:47
  • @LazyCat actually this answer explains the logic quite well. The logic is that `delete iptr2` is undefined. The expression has no meaning, hence anything can happen. And what actually happens is up to the implementation. – 463035818_is_not_an_ai Jan 21 '23 at 15:51
  • @LazyCat • To which rule are you looking for the logic behind? – Eljay Jan 21 '23 at 15:53
  • @Eljay To why you cannot deallocate memory on a heap with delete if it wasn't allocated with new. – LazyCat Jan 21 '23 at 16:04
  • Because this is a part of the C++ standard. It is what it is. This is what the C++ standard specifies. – Sam Varshavchik Jan 21 '23 at 16:07
  • @SamVarshavchik OK, how does it know that a given pointer to a memory block on a heap was constructed with `new`? – LazyCat Jan 21 '23 at 16:15
  • On all the platforms I've worked on, **automatic memory** is allocated from the **stack**. **Dynamic memory** is allocated on the **heap**. – Eljay Jan 21 '23 at 16:15
  • It doesn't, @LazyCat, it expects the programmer who wrote the C++ program to know that, expects that it is, and proceeds with its internal logic, accordingly. – Sam Varshavchik Jan 21 '23 at 16:19
  • It knows that a given pointer to a memory block on a heap was constructed with `new` (and not `new[]` or placement `new`) because the programmer returns that memory using `delete`. It's the programmers responsibility. But if the programmer lies to the compiler, the compiler might do something unexpected. – Eljay Jan 21 '23 at 16:19
  • @SamVarshavchik If it doesn't then it can't apply your rule. And then the only possible answer to my question seems to be: (1) If I try to `delete` a memory block from the stack, it will complain that it's not a heap memory (which it can differentiate). (2) If I try to `delete` a memory block on a heap, it will try to delete it, but the result may depend on the architecture, because it may not know the size of the block to be deleted – LazyCat Jan 21 '23 at 16:26
  • Unfortunately, @LazyCat, C++ does not work this way. There is no requirement that compiled C++ code must detect every instance of undefined behavior, and produce a happy little error message explaining everything. It is the code's responsibility to avoid undefined behavior, otherwise there are no defined results, by definition. This is what makes C++ the most complicated and hardest to learn general purpose programming language in use today. There is no other answer here: this crash is due to undefined behavior. The actual, mechanical, underlying reason for the crash is irrelevant. – Sam Varshavchik Jan 21 '23 at 16:33
1

You can only delete what has been allocated via new.

As explained in the article, placement new, skips the allocation:

Calling placement new directly skips the first step of object allocation. We don't ask for memory from the OS. Rather, we tell it where there's memory to construct the object in [3]. The following code sample should clarify this:

You cannot delete mem because it has not been allocated via new. mem has automatic storage duration and gets freed when main returns.

Placement new in your code creates an int in already allocated memory. If int had a destructor you would need to call the destructor (but not deallcoate the memory). Placing the int in the memory of mem does not change the fact that mem is allocated on the stack.

Actually the placement new in the code is not that relevant for the issue in the code. Also this code

int main(int argc, const char* argv[])
{
    char mem[sizeof(int)];   
    delete iptr2;       // Whoops, Undefined    
    return 0;
}

Has undefined behavior just as your code has.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thanks. heap/stack allocation is a good point. What if I allocate 4 chars on a heap and reinterpret_cast them to an int pointer? Is there a good reason why it still shouldn't be valid? – LazyCat Jan 21 '23 at 15:46
  • @LazyCat if you reinterpret some memory address to a pointer to `int` then there is no `int`. C++ has rules about when and how objects come into existance. They are rather involved and I dont understand all details myself. But with certainty: In your code an `int` is created. If you just reinterpret pointer to `mem` to pointer to `int` and then try to use that `int` you get undefined behavior, because there is no `int` – 463035818_is_not_an_ai Jan 21 '23 at 15:48
  • @LazyCat consider that with completely "normal" use of `new` and `delete` already the implementation has to keep track of the addresses and what has been allocated somehow. `int* x = new int[5]; delete[] x;` this only works because somehow the implementation knows that `x` poitns to an array of size `5`. The implementation makes sure that such stuff works. In your code you broke the contract, hence the assumptions the implemenation makes in order to get the right code to work do not hold, and wrong things will happen, thats undefined behavior – 463035818_is_not_an_ai Jan 21 '23 at 15:55
  • @LazyCat `delete x;` would be undefined behavior as well. Not because there is a rule that says "If you use `new int [5];` and then use use `delete` instead of `delete []` then xy happens", but because there is a rule that says that you need to use `delete []` for what has been allocated via `new []` and you broke that rule – 463035818_is_not_an_ai Jan 21 '23 at 15:57
  • @LazyCat actually "rule" is the wrong word. "definition" is much better. The standard defines what happens when you `delete` an object that has been allocated via `new`. It does not define what happens when you `delete` an object that has not been allocated via `new`. – 463035818_is_not_an_ai Jan 21 '23 at 16:00
  • @LazyCat • Doing something in C++ that is not defined by the language standard is what **undefined behavior** means. In the context of undefined behavior, any observed behavior (crashing, seeming to work, emailing your browser history to your grandmother) is all acceptable behavior. Is there a reason C++ allows this? Yes, for performance. Is it a good rule? That's subjective; if you don't like it there are many other languages to choose from. – Eljay Jan 21 '23 at 16:05
  • @LazyCat and something I compeltely missed to mention... the standard never mentions a crash. There is nothing that says "if you do this then your program will crash". A segfault is only one possible symptom of UB – 463035818_is_not_an_ai Jan 21 '23 at 16:07
1

There are several flavours of new and delete.

The (non-placement) new operator allocates memory by calling the operator new() function. The delete operator frees memory by calling the operator delete() function. They are a pair.

(Confused yet? Read this, or maybe this).

The new[] operator allocates memory by calling the operator new[]() function. The delete[] operator frees memory by calling the operator delete[]() function. They are a different pair.

The placement new operator does not allocate memory and does not call any kind of operator new()-like function. There is no corresponding delete operator or operator delete()-like function.

You cannot mix new of one flavour with delete of a different flavour, it makes no sense and the behaviour is undefined.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243