Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overallocating with new/delete

Using malloc and free, it is easy to allocate structures with extra data beyond the end. But how do I accomplish the same with new/ delete?

I know I could use placement new syntax along with malloc for the allocation part, but will delete work properly and portably if I place an object in memory allocated by malloc?

What I want to accomplish is the same as the following example, but using new/ delete instead of malloc/ free, so that constructors/destructors will be called properly:

#include <cstdlib>
#include <cstring>
#include <iostream>

class Hamburger {
  int tastyness;
 public:
  char *GetMeat();
};

char *Hamburger::GetMeat() {
    return reinterpret_cast<char *>(this) + sizeof(Hamburger);
}

int main(int argc, char* argv[])
{
   Hamburger* hb;
   // Allocate a Hamburger with 4 extra bytes to store a string.
   hb = reinterpret_cast<Hamburger*>(malloc(sizeof(Hamburger) + 4));
   strcpy(hb->GetMeat(), "yum");
   std::cout << "hamburger is " << hb->GetMeat() << std::endl;
   free(hb);
}

Output: hamburger is yum

like image 872
porgarmingduod Avatar asked Apr 02 '11 01:04

porgarmingduod


2 Answers

You can do this without resorting to malloc/free or undefined behavior (I'm not sure about the reinterpret_cast, but at least construction/destruction can be done just fine).

To allocate the memory you can just call the global operator new directly. After that you use good old placement new to construct the object there. You have to guard the ctor-call though, since the "placement delete" function that's called if the ctor fails will not release any memory but just do nothing (just as placement new does nothing).

To destroy the object afterwards you can (and may) call the destructor directly, and to release the memory you can call the global operator delete.

I think it should also be OK to just delete it as you would any normal object, since calling the destructor and global operator delete afterwards is just what the normal delete will do, but I'm not 100% sure.

Your example modified like that:

#include <cstdlib>
#include <cstring>
#include <iostream>

class Hamburger {
    int tastyness;
public:
    char *GetMeat();
};

char *Hamburger::GetMeat() {
    return reinterpret_cast<char *>(this) + sizeof(Hamburger);
}

int main(int argc, char* argv[])
{
    Hamburger* hb;
    // Allocate space for a Hamburger with 4 extra bytes to store a string.
    void* space = operator new(sizeof(Hamburger) + 4);
    // Construct the burger in that space
    hb = new (space) Hamburger; // TODO: guard ctor call (release memory if ctor fails)
    strcpy(hb->GetMeat(), "yum"); // OK to call member function on burger now

    std::cout << "hamburger is " << hb->GetMeat() << std::endl;

    // To delete we have to do 2 things
    // 1) call the destructor
    hb->~Hamburger();
    // 2) deallocate the space
    operator delete(hb);
}
like image 142
Paul Groke Avatar answered Sep 23 '22 03:09

Paul Groke


If I were you, I'd use placement new and an explicit destructor call instead of delete.

template< typename D, typename T >
D *get_aux_storage( T *x ) {
    return reinterpret_cast< D * >( x + 1 );
}

int main() {
    char const *hamburger_identity = "yum";
    void *hamburger_room = malloc( sizeof( Hamburger )
                                   + strlen( hamburger_identity ) + 1 );
    Hamburger *hamburger = new( hamburger_room ) Hamburger;
    strcpy( get_aux_storage< char >( hamburger ), hamburger_identity );
    cout << get_aux_storage< char const >( hamburger ) << '\n';

    hamburger->~Hamburger(); // explicit destructor call
    free( hamburger_room );
}

Of course, this kind of optimization should only be done after profiling has proven the need. (Will you really save memory this way? Will this make debugging harder?)

There might not be a significant technical difference, but to me new and delete signal that an object is being created and destroyed, even if the object is just a character. When you allocate an array of characters as a generic "block," it uses the array allocator (specially suited to arrays) and notionally constructs characters in it. Then you must use placement new to construct a new object on top of those characters, which is essentially object aliasing or double construction, followed by double destruction when you want to delete everything.

It's better to sidestep the C++ object model with malloc/free than to twist it to avoid dealing with data as objects.

Oh, an alternative is to use a custom operator new, but it can be a can of worms so I do not recommend it.

struct Hamburger {
  int tastyness;
public:
  char *GetMeat();
  static void *operator new( size_t size_of_bread, size_t size_of_meat )
      { return malloc( size_of_bread + size_of_meat ); }
  static void operator delete( void *ptr )
      { free( ptr ); }
};

int main() {
    char const *hamburger_identity = "yum";
    size_t meat_size = strlen( hamburger_identity ) + 1;
    Hamburger *hamburger = new( meat_size ) Hamburger;
    strcpy( hamburger->GetMeat(), hamburger_identity );
    cout << hamburger->GetMeat() << '\n';
}
like image 35
Potatoswatter Avatar answered Sep 23 '22 03:09

Potatoswatter