Mismatching scalar and vector new and delete

Date:February 3, 2004 / year-entry #47
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20040203-00/?p=40763
Comments:    16
Summary:In a previous entry I alluded to the problems that can occur if you mismatch scalar "new" with vector "delete[]" or vice versa. There is a nice description of C++ memory management in C++ Gotchas: Avoiding Common Problems in Coding and Design on www.informit.com, and I encourage you to read at least the section titled...

In a previous entry I alluded to the problems that can occur if you mismatch scalar "new" with vector "delete[]" or vice versa.

There is a nice description of C++ memory management in C++ Gotchas: Avoiding Common Problems in Coding and Design on www.informit.com, and I encourage you to read at least the section titled Failure to Distinguish Scalar and Array Allocation before continuing with this entry, because I'm going to use that information as a starting point.

Here's how the Microsoft C++ compiler manages vector allocation. Note that this is internal implementation detail, so it's subject to change at any time, but knowing this may give a better insight into why mixing scalar and vector new/delete is a bad thing.

The important thing to note is that when you do a scalar "delete p", you are telling the compiler, "p points to a single object." The compiler will call the destructor once, namely for the object you are destructing.

When you do "delete[] p", you are saying, "p points to a bunch of objects, but I'm not telling you how many." In this case, the compiler needs to generate extra code to keep track of how many it needs to destruct. This extra information is kept in a "secret place" when the vector is allocated with "new[]".

Let's watch this in action:


class MyClass {
public:
  MyClass(); // constructor
  ~MyClass(); // destructor
  int i;
};

MyClass *allocate_stuff(int howmany)
{
    return new MyClass[howmany];
}

The generated code for the "allocate_stuff" function looks like this:

    push    esi
    mov     esi, [esp+8] ; howmany
   ;  eax = howmany * sizeof(MyClass) + sizeof(size_t)
    lea     eax, [esi*4+4]
    push    eax
    call    operator new
    test    eax, eax
    pop     ecx
    je      fail
    push    edi
    push    OFFSET MyClass::MyClass
    push    esi
    lea     edi, [eax+4] ; edi = eax + sizeof(size_t)
    push    4 ; sizeof(MyClass)
    push    edi
    mov     [eax], esi ; howmany
    call    `vector constructor iterator'
    mov     eax, edi
    pop     edi
    jmp     done
fail:
    xor     eax, eax
done:
    pop     esi
    retd    4

Translated back into pseudo-C++, the code looks like this:

MyClass* allocate_stuff(int howmany)
{
  void *p = operator new(
     howmany * sizeof(MyClass) + sizeof(size_t));
  if (p) {
    size_t* a = reinterpret_cast<size_t*>(p);
    *a++ = howmany;
    vector constructor iterator(a,
      sizeof(MyClass), &MyClass::MyClass);
    return reinterpret_cast<MyClass*>(a);
  }
  return NULL;
}

In other words, the memory layout of the vector of MyClass objects looks like this:

howmany
MyClass[0]
MyClass[1]
...
MyClass[howmany-1]

The pointer returned by the new[] operator is not the start of the allocated memory but rather points to MyClass[0]. The count of elements is hidden in front of the vector.

The deletion of a vector performs this operation in reverse:

void free_stuff(MyClass* p)
{
    delete[] p;
}

generates

    mov     ecx, [esp+4] ; p
    test    ecx, ecx
    je      skip
    push    3
    call    MyClass::`vector deleting destructor`
skip
    ret     4

Translated back into pseudo-C++, the code looks like this:

void free_stuff(MyClass* p)
{
  if (p) p->vector deleting destructor(3);
}

The vector deleting destructor goes like this (pseudo-code):

void MyClass::vector deleting destructor(int flags)
{
  if (flags & 2) { // if vector destruct
    size_t* a = reinterpret_cast<size_t*>(this) - 1;
    size_t howmany = *a;
    vector destructor iterator(p, sizeof(MyClass),
      howmany, MyClass::~MyClass);
    if (flags & 1) { // if delete too
      operator delete(a);
    }
  } else { // else scalar destruct
    this->~MyClass(); // destruct one
    if (flags & 1) { // if delete too
      operator delete(this);
    }
  }
}

The vector deleting destructor takes some flags. If 2 is set, then a vector is being destructed; otherwise a single object is being destructed. If 1 is set, then the memory is also freed.

In our case, the flags parameter is 3, so we will perform a vector destruct followed by a delete. Observe that this code sucks the original "howmany" value out of its secret hiding place and asks the vector destructor iterator to run the destructor that many times before freeing the memory.

So now, armed with this information, you should be able to describe what happens if you allocate memory with scalar "new" and free it with vector "delete[]" or vice versa.

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

Answers to come tomorrow.


Comments (16)
  1. Johan Ericsson says:

    That must be the first psuedo-code that I’ve seen that uses reinterpret_cast<>. Cool!

    Of course, if you use new[] and then delete only a single destructor gets called. That is the destructor for the first element. However, I’m not sure what the outcome of deleting the first element is as compared to deleting the hidden count. Perhaps the count gets leaked as well. I don’t remember this use case causing any of the CRT leaking routines to report any memory leaking? So, I’m not sure about this part.

    If you use new and then delete[], then you are really in trouble. You will first call a destructor an unspecified number of times, depending on the random value of the hidden count. Then you will try to delete the hidden count, which isn’t memory that you’ve allocated. Seems like you will crash.

    These are some more reasons to use a vector<> to wrap the array pointer, instead of doing your own memory management.

    I seem to recall that Andrei Alexandrescu wrote some interesting articles for CUJ (C++ User’s Journal) where he was trying to create a super vector<>. He complained that it was obvious that the compiler was keeping track of the count, yet the vector also has to keep track of the count. This seems inefficient… Its too bad that C++ doesn’t provide a Standard way of getting a hold of that count.

  2. Wouldn’t it be possible to let scalar-new be identical to "new T[1]"? That way calling vector-delete on a scalar-new would work fine.

    It would of course be a little bit more inefficient to delete single elements as well as use more memory. So maybe it’s a bad idea to make illegal programs work on the cost of legal ones…

  3. runtime says:

    All of these problems could be solved if new (silently) returned a 1-element array. Then calling delete or delete[] would do the same thing: check the p[-1] size count and then calling the destructors. The debug version of delete could assert(p[-1] == 1), alerting the programmer if he called delete on an array when he should have called delete[].

  4. Joe says:

    The assert wouldn’t be accurate, as it is possible to new Foo[1] (and even new Foo[0] is legal!).

    What will actually happen if you use scalar delete for array new is that it will destroy a single object, then pass the pointer to free() which will back up to get ITS own count but will instead get the object count. It will then free too little, and most likely corrupt the heap. Unless your malloc uses a hashtable of addresses to store size data, then you will probably corrupt the heap.

    This behavior is dictated by the standard. As a developer, you should do the right thing always and not rely on the compiler to fix things for you. Always run your code under Valgrind or BoundsChecker or Purify to find these kinds of problems.

  5. Raymond Chen says:

    Johan, tomorrow's entry will discuss some of your points. In particular, the Bonus Exercise will explain why there is no "Give me the number of elements in this dynamically-allocated array" operator.

  6. Joe says:

    I should also point out that none of the suggestions will save you from:

    Foo *p = new Foo[10];

    delete p+1;

    If the concept of array and scalar new and delete frighten you, use malloc and placement new.

    Foo *p = (Foo *)malloc(sizeof(Foo));

    new (p) Foo;

    p->blah();

    p->~Foo();

    free(p);

    Amaze your friends! Alienate your co-workers!

  7. Ben Wilhelm says:

    One of my favorite recurring "programmers who know just a little too much for their own good" stories:

    "Oh, it’s easy to get the number of items in a C-style array! You just do ((int*)array)[-1]! It works every time! Why are you brandishing that axe?"

    Quick, how many different ways can you find that would cause that to break? :P

  8. Doug says:

    Yet another reason why C++ is a broken language.

    Raymond, if you are looking for another topic, how about discussing the problems with MFC and different versions of the compiler. Like what happens when you build an OCX with MFC, then try to have it work in both IE5 and IE6, without corrupting memory. Just another version of DLL hell. Which is compounded by OCXs loading from the registry and not the path.

    Or you can just rewrite the silly thing in ATL and be done with the problem.

  9. Shane King says:

    If you don’t have any destructor, then the compiler doesn’t need to do any cleanup on delete, and can just free the memory. Therefore it can not bother allocating extra space for the size of the array.

    In this case, the "give me the number of elements in the array" would have nothing to look at.

    Additionally, if the compiler performs this optimisation, then a mismatched new[]/delete will work; right up until someone changes the code to have a destructor, then you’ll be wondering why your program crashes all the time from such a "trivial" change.

  10. Centaur says:

    > If you don’t have any destructor, then the

    > compiler doesn’t need to do any cleanup on

    > delete, and can just free the memory.

    That is, if you don’t have any /user-declared/ destructor, and the /implicitly-declared/ destructor is /trivial/ [12.4.3].

  11. James Curran says:

    Shane,

    How is it "bothering" to allocate extra space? Allocating 104 bytes is exactly the same work as allocating 100. You just have to add four to the number you pass to malloc().

  12. Ben Hutchings says:

    A question for Raymond: where’s the exception handling code for allocate_stuff? Given that this is x86 code, some of that must be generated inline. Did the compiler somehow know that MyClass’s constructor can’t throw?

  13. Shane King says:

    I meant in the sense that if you don’t allocate the extra space, your program uses less memory, which is a good thing. Also, if you don’t do the work to store the size there, you also run marginally quicker.

    It’s a pretty small optimisation really, but some compilers do it.

    And yes, I realise that destructors don’t have to be declared to exist. I was speaking informally, rather than in C++ language specese.

  14. Raymond Chen says:

    Doug: I don’t use MFC myself and have never learned it, so I can’t talk about it with any degree of confidence.

    Ben: I compiled with non-throwing allocation, which is why you don’t see any throw-handling.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index