Overallocating with new/delete

邮差的信 提交于 2019-12-18 02:49:35

问题


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


回答1:


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';
}



回答2:


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);
}



回答3:


Urgh. Well, let's see. You definitely can't allocate with new/malloc and dispose with free/delete. You have to use matching pairs.

I suppose you could use "hp = new char[sizeof(Hamburger) + 4]" and "delete[]((char *) hp)", along with explicit constructor/destructor calls, if you really wanted to do this.

The only reason I can think why you'd want to do this would be you didn't have the Hamburger source -- i.e., it was a library class. Otherwise you'd just add a member to it! Can you explain why you'd want to use this idea?




回答4:


There is another way that you could approach this if you have a reasonably constrained set of padding amounts. You could make a template class with the padding amount as the template parameter and then instantiate it with the set of possible padding amounts. So if, for example, you knew that you were only going to need padding of 16, 32, or 64 bytes, you could do it like this:

template <int Pad>
class Hamburger {
    int tastiness;
    char padding[Pad];
};

template class Hamburger<16>;
template class Hamburger<32>;
template class Hamburger<64>;



回答5:


Is there any reason why the straightforward, easy and safe way is not applicable?

class Hamburger {
public:
    void Extend( const std::string& pExtension) {
        mContent += pExtension;
    }
    const std::string& GetMeat() ...

private:
    std::string mContent;
};

int main() {
    Hamburger hb;
    hb.Extend("yum");
    std::cout << "hamburger is " << hb.GetMeat() << std::endl;
}


来源:https://stackoverflow.com/questions/5520591/overallocating-with-new-delete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!