7

Consider the program below. It has been simplified from a complex case. It fails on deleting the previous allocated memory, unless I remove the virtual destructor in the Obj class. I don't understand why the two addresses from the output of the program differ, only if the virtual destructor is present.

// GCC 4.4
#include <iostream>

using namespace std;

class Arena {
public:
    void* alloc(size_t s) {
        char* p = new char[s];
        cout << "Allocated memory address starts at: " << (void*)p << '\n';
        return p;
    }

    void free(void* p) {
        cout << "The memory to be deallocated starts at: " << p << '\n';
        delete [] static_cast<char*> (p); // the program fails here
    }
};

struct Obj {
    void* operator new[](size_t s, Arena& a) {
        return a.alloc(s);
    }

    virtual ~Obj() {} // if I remove this everything works as expected

    void destroy(size_t n, Arena* a) {
        for (size_t i = 0; i < n; i++)
            this[n - i - 1].~Obj();
        if (a)
            a->free(this);
    }
};


int main(int argc, char** argv) {
    Arena a;

    Obj* p = new(a) Obj[5]();
    p->destroy(5, &a);

    return 0;
}

This is the output of the program in my implementation when the virtual destructor is present:

Allocated memory address starts at: 0x8895008 The memory to be deallocated starts at: 0x889500c

RUN FAILED (exit value 1)

Please don't ask what the program it's supposed to do. As I said it comes from a more complex case where Arena is an interface for various types of memory. In this example the memory is just allocated and deallocated from the heap.

Martin
  • 9,089
  • 11
  • 52
  • 87

2 Answers2

5

this is not the pointer returned by the new at line char* p = new char[s]; You can see that the size s there is bigger than 5 Obj instances. The difference (which should be sizeof (std::size_t)) is in additional memory, containing the length of the array, 5, immediately before the address contained in this.

OK, the spec makes it clear:

http://sourcery.mentor.com/public/cxx-abi/abi.html#array-cookies

2.7 Array Operator new Cookies

When operator new is used to create a new array, a cookie is usually stored to remember the allocated length (number of array elements) so that it can be deallocated correctly.

Specifically:

No cookie is required if the array element type T has a trivial destructor (12.4 [class.dtor]) and the usual (array) deallocation function (3.7.3.2 [basic.stc.dynamic.deallocation]) function does not take two arguments.

So, the virtual-ness of the destructor is irrelevant, what matters is that the destructor is non-trivial, which you can easily check, by deleting the keyword virtual in front of the destructor and observe the program crashing.

Community
  • 1
  • 1
chill
  • 16,470
  • 2
  • 40
  • 44
  • Yes, I don't really care about the size. I care about the reason why this does not match the pointer alloc() has given for placing the object. – Martin Nov 24 '11 at 21:03
  • @Martin, because `alloc()` has allocated additional space for the length of the array and array elements follow. I don't really know right now why it doesn't store the array length if there's no virtual destructor, have to check the C++ ABI spec. – chill Nov 24 '11 at 21:11
  • What spec is this? My C++ spec has no mention of cookies. – CB Bailey Nov 24 '11 at 22:11
  • @CharlesBailey, the GCC C++ ABI spec, http://sourcery.mentor.com/public/cxx-abi/abi.html – chill Nov 24 '11 at 22:36
  • @chill: Thanks. Perhaps you should put a reference to what you are quoting in your answer. – CB Bailey Nov 24 '11 at 22:42
0

Based on chills' answer, if you want to make it "safe":

#include <type_traits>

a->free(this - (std::has_trivial_destructor<Obj>::value ? 1 : 0));
ronag
  • 49,529
  • 25
  • 126
  • 221
  • 1
    Not, not really, perhaps `a->free ((std::size_t *)this - (std::has_trivial_destructor::value ? 1 : 0));`, but it's strictly speaking UD and I'm sure it'll break in some obscure corner case and/or different compiler ;) – chill Nov 24 '11 at 21:32