Answers to exercises – mismatching new/delete

Answers to yesterday's exercises:

What happens if you allocate with scalar "new" and free with vector "delete[]"?

The scalar "new" will allocate a single object with no hidden counter. The vector "delete[]" will look for the hidden counter, which isn't there, so it will either crash (accessing nonexistent memory) or grab a random number and attempt to destruct that many items. If the random number is greater than one, you will start corrupting memory after the object. If the random number is zero, you fail to destruct anything. If the random number is exactly one, then the one object is destructed.

Next, the vector "delete[]" will attempt to free the memory block starting one size_t in front of the actual memory block. Depending on how the heap feels today, this may be detected as an invalid parameter and ignored, or this can result in heap corruption.

Final result: not good.

What happens if you allocate with vector "new[]" and free with scalar "delete"?

The vector "new[]" allocates several objects and stores the "howmany" in the hidden counter. The scalar "delete" destructs the first object in the vector. If it was a vector of zero objects, you corrupted memory. If it was a vector of two or more objects, then objects 2 an onward will not be destructed. (Result: Memory or other leak.)

Next, the scalar "delete" will free the memory block directly, which will fail because the memory block actually starts at the hidden size_t in front of the vector. This again corrupts the heap since you are freeing memory that is not a valid heap pointer.

Final result: also not good.

What optimizations can be performed if the destructor MyClass::~MyClass() is removed from the class definition?

If the class does not have a destructor, then no special work needs to be done when the vector is freed aside from freeing the memory. In this case, no hidden counter is necessary; the block can be allocated directly with no overhead and freed with no overhead.

More specifically, if the class has a trivial destructor (none of its base classes or sub-objects - if any - have a destructor), then the scalar and vector new/delete allocate and free the memory the same way, and mixing them does not generate a runtime error. You got lucky.

Of course, somebody might add a destructor to your class tomorrow, and then you won't be so lucky any more.

Note of course that all of this discussion assumes compiler behavior as described yesterday. That behavior is implementation-dependent so you should not rely on it. You may be lucky today, but the next version of the compiler may change the way it manages vectors and your luck will have run out.

Comments (13)
  1. Jack Mathews says:

    "What optimizations can be performed if the destructor MyClass::~MyClass() is removed from the class definition?"

    Well, unless you have sub objects in your class with destructors, in which case the compiler will generate a default hidden dtor function for you.

    [I've revised the text to be clearer, thanks. -Raymond]
  2. All classes have destructors, even if they aren’t declared in the code (implicitly declared) and even if the destructor doesn’t need to do anything (trivial destructor). Similarly all classes have at least one constructor and at least one function overloading the assignment operator. This might change in the next version of C++.

  3. B.Y. says:

    This looks like a really user-hostile design of C++.

    Why can’t they just put in the spec: if delete is called on a pointer return by new, it deletes the object pointed; if it’s called on a pointer returned by new[], it deletes the entire array. The compiler can always store a count of 1 for the scalar form of new, and we won’t have this mismatched new/delete bug any more.

    Any reasons they didn’t do that ? Do they enjoy watching programmers shoot themselvs in the foot ?

  4. Shane King says:

    While strictly speaking they have a destructor that doesn’t do anything, I find it more useful to think of them as not having a destructor. While to say as such is not correct as per the C++ spec, it makes talking about it a hell of a lot easier. If you get all nitpicky in the way you talk about C++, you end up with disclaimers longer than the article. ;)

  5. Shane King says:

    "Any reasons they didn’t do that ? Do they enjoy watching programmers shoot themselvs in the foot ?"

    Because the C++ philosophy is "no overhead for things you don’t use". Therefore, it’s unacceptable to add overhead in time and space to non-array allocations by treating them as arrays.

    Whether or not the philosophy is a good thing is debatable, but within the context of the philosophy, some of the design decisions made in C++ make a lot more sense.

  6. runtime says:

    B.Y., I made the same suggestion yesterday and some people blew a gasket! I think they (and Bjarne) do not want to waste those "unnecssary" sizeof(size_t) bytes for a scalar object. Even in debug-only code, this would be very helpful. Alas, they didn’t think a compiler’s job is to debug programmer errors. That is a job for Purify, BoundsChecker, or Valgrind.

  7. Raymond Chen says:

    For "If the class does not have a destructor" I should have written "If the class destructor is trivial". Sorry for the lack of clarity.

  8. Jonathan O'Connor says:

    Stan Lippmann wrote a terrific book about the internals of C++ implementations. Highly recommended for reading on the bus/train to work!

  9. Joe says:

    All my gaskets are intact, thank you.

    Like I mentioned yesterday, it’s not trivial to tell the difference between memory allocated by scalar new and memory allocated by array new, since array new can take any value from 0 to 0xFFFFFFFF. This means that debug code would have to add more than just a size_t in order to keep track of this change, at which point the debug code is markedly different from release code. If you want to do this kind of checking, there are plenty of ways you can do it yourself without requiring the compiler to break the C++ standard.

    The compiler’s job is not to debug run-time errors such as mismatched new/delete operators (as a corollary, the compiler isn’t there to warn you if you call delete twice, or call free() on a new’d object). All of this checking puts overhead into the code that isn’t mandated by the spec and may be unwanted in many cases (some people don’t want the code for their real-time application to be slowed down by excess checks against bad memory handling).

    The bottom line is that the onus is on the developer to not write buggy code. We have a mantra where I work — "Careful!" It reminds us that we have to always be careful writing code and take the time to make sure things are done right, likewise if you aren’t careful, you shouldn’t be writing code. Mis-matching new and delete is not careful and if you rely on a compiler crutch to keep you from doing it then you probably shouldn’t be writing code at that level. If it’s really a big problem, use a language that doesn’t put such a large responsibility on the developer.

    All gaskets still intact.

  10. B.Y. says:

    Yes, given the current standard, the compiler has to do what it’s doing now.

    The real-time argument makes no sense. If their code is that time-critical, they shouldn’t be allocating and deleting objects/memory in the real-time code in first place.

    Sure, we should be careful, but that’s beside the point. How would you like it if the standard says all pointers should be treated as byte pointers in pointer arithmetic ? So instead of writing "p+n" you have to carefully write "p+n*sizeof(whatever)".

    We’re not saying that we should rely on a compiler crutch, but a properly written spec can eliminate that bug altogether.

  11. zz says:

    So, if you allocate a primitive type like int or char using the scalar new and free it using the vector delete, then it should be OK since there are no "destructors" for primitives? Also, how about mismatch frees i.e. allocate with malloc() but freed using delete? What are the problems with those scenarios?

  12. Raymond Chen says:

    If you allocate with "new[]" you must free with "delete[]". If you allocate with "new" you must free with "delete". If you allocate with "malloc", "calloc" or "realloc" you must free with "free". If you allocate with "LocalAlloc" you must free with "LocalFree". If you allocate with "GlobalAlloc" you must free with "GlobalFree". If you allocate with "CoTaskMemAlloc" you must free with "CoTaskMemFree".

    It is always an error to allocate memory with one allocator and free with a different allocator.

  13. Ziv Caspi says:

    Re "If you allocate with "new[]" you must free with "delete[]".":

    Don’t forget to make sure that the allocation/deallocation functions come from the same instance of your runtime library. Forgetting to do that is asking for trouble.

    (This is why self-deallocation (as in calling "delete this" from a member function) is so popular in some circles.)

Comments are closed.

Skip to main content