About placement new and delete

source:http://en.wikipedia.org/wiki/Placement_syntax#cite_note-Yongwei2007-13

Placement syntax

From Wikipedia, the free encyclopedia
Jump to: navigation, search

In the C++ programming language, placement syntax allows programmers to explicitly specify thememory management of individual objects — i.e. their "placement" inmemory. Normally, when an object is created dynamically, an allocation function is invoked in such a way that it will both allocate memory for the object, andinitialize the object within the newly allocated memory. The placement syntax allows the programmer to supply additional arguments to the allocation function. A common use is to supply apointer to a suitable region of storage where the object can be initialized, thus separating memory allocation from object construction.[citation needed]

The "placement" versions of the new anddelete operators and functions are known as placementnew and placement delete.[1] Anew expression, placement or otherwise, calls a newfunction, also known as an allocator function, whose name is operator new. Similarly, adelete expression calls a delete function, also known as a deallocator function, whose name isoperator delete.[2][3]

Any new expression that uses the placement syntax is a placement new expression, and any operator new or operator delete function that takes more than the mandatory first parameter (std::size_t and void *, respectively) is a placement new or placement delete function.[4]

Contents

[hide]

[edit]History

In earlier versions of C++ there was no such thing as placement new; instead, developers used explicit assignment tothis within constructors to achieve similar effect.[5] This practice has been deprecated and abolished later, and third edition of The C++ Programming Language doesn't mention this technique. Support forplacement new operator has been added to compilers circa 1995.[citation needed]

[edit]Expressions

The Standard C++ syntax for a non-placement new expression is[2]

new new-type-id ( optional-initializer-expression-list )

The placement syntax adds an expression list immediately after the new keyword. This expression list is the placement. It can contain any number of expressions.[2][3][6]

new ( expression-list ) new-type-id ( optional-initializer-expression-list )

There is no placement delete expression.[7]

[edit]Functions

The placement new functions are overloads of the non-placement new functions. The declaration of the non-placement new functions, for non-array and arraynew expressions respectively, are:[8][9]

void * operator new (std :: size_t ) throw (std :: bad_alloc ) ;
void * operator new [ ] (std :: size_t ) throw (std :: bad_alloc ) ;

The Standard C++ library provides two placement overloads each for these functions. Their declarations are:[8][9]

void * operator new (std :: size_t, const std :: nothrow_t & ) throw ( ) ;
void * operator new (std :: size_t, void * ) throw ( ) ;
void * operator new [ ] (std :: size_t, const std :: nothrow_t & ) throw ( ) ;
void * operator new [ ] (std :: size_t, void * ) throw ( ) ;

In all of the overloads, the first parameter to the operator new function is of typestd::size_t, which when the function is called will be passed as an argument the amount of memory, in bytes, to allocate. All of the functions must return typevoid *, which is a pointer to the storage that the function allocates.[2]

There are also placement delete functions. They are overloaded versions of the non-placement delete functions. The non-placement delete functions are declared as:[8][9]

void operator delete ( void * ) throw ( ) ;
void operator delete [ ] ( void * ) throw ( ) ;

The Standard C++ library provides two placement overloads each for these functions. Their declarations are:[8][9]

void operator delete ( void *, const std :: nothrow_t & ) throw ( ) ;
void operator delete ( void *, void * ) throw ( ) ;
void operator delete [ ] ( void *, const std :: nothrow_t & ) throw ( ) ;
void operator delete [ ] ( void *, void * ) throw ( ) ;

In all of the overloads, the first parameter to the operator delete function is of typevoid *, which is the address of the storage to deallocate.[2]

For both the new and the delete functions, the functions are global, are not in any namespace, and do not have static linkage.[2]

[edit]Use

Placement syntax has four main uses: default placement, preventing exceptions, custom allocators, and debugging.

[edit]Default placement

The placement overloads of operator new and operator delete that employ an additionalvoid * parameter are used for default placement, also known as pointer placement. Their definitions by the Standard C++ library, which it is not permitted for a C++ program to replace or override, are:[8][9][10]

void * operator new (std :: size_t, void * p ) throw ( ) { return p ; }
void * operator new [ ] (std :: size_t, void * p ) throw ( ) { return p ; }
void operator delete ( void *, void * ) throw ( ) { }
void operator delete [ ] ( void *, void * ) throw ( ) { }

Default placement does not require the inclusion of the Standard C++ library header<new> in the source code of a C++ program.[2] However, g++ version 4.0 still requires the use of this header (this might be true of other versions of the compiler or other compilers too.)[citation needed]

There are various uses for default placement.

Bjarne Stroustrup originally observed, in his bookThe Design and Evolution of C++, that pointer placement new is necessary for hardware that expects a certain object at a specific hardware address. It is also required for the construction of objects that need to reside in a certain memory area, such as an area that is shared between several processors of a multiprocessor computer.[11]

Other uses, however, include calling a constructor directly, something which the C++ language does not otherwise permit.[3]

The C++ language does allow a program to call a destructor directly, and, since it is not possible to destroy the object using adelete expression, that is how one destroys an object that was constructed via a pointer placement new expression. For example:[7][12]

p - >~T ( ) ;

[edit]Preventing exceptions

Normally, the (non-placement) new functions throw an exception, of type std::bad_alloc, if they encounter an error, such as exhaustion of all available memory. This was not how the functions were defined by Stroustrup'sAnnotated C++ Reference Manual, but was a change made by the standardization committee when the C++ language was standardized. The original behaviour of the functions, which was to return aNULL pointer when an error occurred, is accessible via placement syntax.[3][4][6]

Programmers who wish to do this in their programs must include the Standard C++ library header<new> in the source code. This header declares the global std::nothrow object, which is of typestd::nothrow_t (also declared in the header), which is used to call the overloaded new functions that are declared as takingconst std::nothrow_t& as their second parameter. For example:[10]

#include <new>

struct T { } ;

int main ( )
{
    // Call the function operator new(std::size_t, const std::nothrow_t &) and (if successful) construct the object.
    T * p = new (std :: nothrow ) T ;
    if (p ) {
        // The storage has been allocated and the constructor called.
        delete p ;
    } else
        ; // An error has occurred.  No storage has been allocated and no object constructed.
    return 0 ;
}

[edit]Custom allocators

Placement syntax is also employed for custom allocators. This does not use any of the allocator and deallocator functions from the Standard C++ library header<new>, but requires that programmers write their own allocation and deallocation functions, overloaded for user-defined types. For example, one could define a memory management class as follows:[8][9]

#include <cstdlib>
class A {
public :
    void * allocate ( std :: size_t ) ;
    void deallocate ( void * ) ;
} ;

And define custom placement allocation and deallocation functions as follows:[8][9]

void *
operator new (std :: size_t size, A & arena )
{
    return arena. allocate (size ) ;
}
void
operator delete ( void * p, A & arena )
{
    arena. deallocate (p ) ;
}

The program would employ the placement syntax to allocate objects using different instances of theA class as follows:[8][9]

A first_arena, second_arena ;
T * p1 = new (first_arena ) T ;
T * p2 = new (second_arena ) T ;

Destroying an object whose storage is allocated in such a fashion requires some care. Because there is no placement delete expression, one cannot use it to invoke the custom deallocator. One must either write a destruction function that invokes the custom deallocator, or call the placement delete function directly, as a function call.[7][8][9]

The former would resemble:[9]

void
destroy (T * p, A & arena )
{
    p - >~T ( ) ;   // First invoke the destructor explicitly.
    arena. deallocate (p ) ;  // Then call the deallocator function directly.
}

which would be invoked from a program as:

A arena ;
T * p = new (arena ) T ;
/* ... */
destroy (p, arena ) ;

The latter would involve simply writing the destructor invocation and delete function call into the program:[8][13]

A arena ;
T * p = new (arena ) T ;
/* ... */
p - >~T ( ) ;    // First invoke the destructor explicitly.
operator delete (p, arena ) ;   // Then call the deallocator function indirectly via operator delete(void *, A &) .

A common error is to attempt to use a delete expression to delete the object. This results in the wrongoperator delete function being called. Dewhurst recommends two strategies for avoiding this error. The first is to ensure that any custom allocators rely upon the Standard C++ library's global, non-placement,operator new, and are thus nothing more than simple wrappers around the C++ library's memory management. The second is to create new and delete functions for individual classes, and customize memory management via class function members rather than by using the placement syntax.[13]

[edit]Debugging

Placement new can also be used as a simple debugging tool, to enable programs to print the filename and line number of the source code where a memory allocation has failed. This does not require the inclusion of the Standard C++ library header<new>, but does require the inclusion of a header that declares four placement functions and a macro replacement for thenew keyword that is used in new expressions. For example, such a header would contain:[10][14]

#if defined(DEBUG_NEW)
void * operator new (std :: size_t size, const char * file, int line ) ;
void * operator new [ ] (std :: size_t size, const char * file, int line ) ;
void operator delete ( void * p, const char * file, int line ) ;
void operator delete [ ] ( void * p, const char * file, int line ) ;
#define New new(__FILE__, __LINE__)
#else
#define New new
#endif

This would be employed in a program as follows:[10][14]

T * p = New T ;

The custom-written placement new functions would then handle using the supplied file and line number information in the event of an exception. For example:[10][14]

#include <new>
#include <cstdlib>

class NewError {
public :
    NewError ( const char * file, int line ) { /* ... */ }
    /* ... */
} ;

void *
operator new (std :: size_t size, const char * file, int line )
{
    if ( void * p = :: operator new (size, std :: nothrow ) )
        return p ;
    throw NewError (file, line ) ;
}

[edit]Placement delete

As noted above, there is no placement delete expression. It is not possible to callany placement operator delete function using a delete expression.[7][15]

The placement delete functions are called from placement new expressions. In particular, they are called if theconstructor of the object throws an exception. In such a circumstance, in order to ensure that the program does not incur a memory leak, the placement delete functions are called. A placement new expression first calls the placementoperator new function, then calls the constructor of the object upon the raw storage returned from the allocator function. If the constructor throws an exception, it is necessary to deallocate that storage before propagating the exception back to the code that executed the placement new expression, and that is the purpose of the placement delete functions.[2][4][7][15]

The placement delete function that is called matches the placement new function that was invoked by the placement new expression. So, for example, if the following code is executed, the placement delete function that is called will beoperator delete(void *, const A &):[2][7][15]

#include <cstdlib>

struct A { } ;
struct E { } ;

class T {
public :
    T ( ) { throw E ( ) ; }
} ;

void * operator new ( std :: size_t, const A & ) ;
void operator delete ( void *, const A & ) ;

int main ( )
{
    A a ;
    T * p = new (a ) T ;
    return 0 ;
}

This is why the pointer placement delete functions are defined as no-operations by the Standard C++ library. Since the pointer placement new functions do not allocate any storage, there is no storage to be deallocated in the event of the object's constructor throwing an exception.[7]

If no matching placement delete function exists, no deallocation function is called in the event of an exception being thrown by a constructor within a placementnew expression. There are also some (older) C++ implementations that do not support placement delete (which, like the exception-throwing allocator functions, were an addition made to C++ when it was standardized) at all. In both such situations, an exception being thrown by a constructor when allocating using a custom allocator will result in a memory leak. (In the case of the older C++ implementations, a memory leak will also occur withnon-placement new expressions.)[4][15]

 

 

source: http://www.parashift.com/c++-faq-lite/dtors.html

 Is there a way to force new to allocate memory from a specific memory area?

Yes.  The good news is that these "memory pools" are useful in a number of situations.  The bad news is that I'll have to drag you through the mire of how it works before we discuss all the uses.  But if you don't know about memory pools, it might be worthwhile to slog through this FAQ — you might learn something useful!

First of all, recall that a memory allocator is simply supposed to return uninitialized bits of memory; it is not supposed to produce "objects." In particular, the memory allocator is not supposed to set the virtual-pointer or any other part of the object, as that is the job of the constructor which runs after the memory allocator.  Starting with a simple memory allocator function, allocate() , you would useplacementnew to construct an object in that memory.  In other words, the following is morally equivalent to new Foo() :

void* raw = allocate(sizeof(Foo));  // line 1
Foo* p = new(raw) Foo();           
// line 2

Okay, assuming you've used placement new and have survived the above two lines of code, the next step is to turn your memory allocator into an object.  This kind of object is called a "memory pool" or a "memory arena." This lets your users have more than one "pool" or "arena" from which memory will be allocated.  Each of these memory pool objects will allocate a big chunk of memory using some specific system call (e.g., shared memory, persistent memory, stack memory, etc.; see below), and will dole it out in little chunks as needed.  Your memory-pool class might look something like this:

class Pool {
public:
   void* alloc(size_t nbytes);
   void dealloc(void* p);
private:
  
...data members used in your pool object...
};

void* Pool::alloc(size_t nbytes)
{
  
...your algorithm goes here...
}

void Pool::dealloc(void* p)
{
  
...your algorithm goes here...
}

Now one of your users might have a Pool called pool, from which they could allocate objects like this:

Pool pool;
...
void* raw = pool.alloc(sizeof(Foo));
Foo* p = new(raw) Foo();

Or simply:

Foo* p = new(pool.alloc(sizeof(Foo))) Foo();

The reason it's good to turn Pool into a class is because it lets users createN different pools of memory rather than having one massive pool shared by all users.  That allows users to do lots of funky things.  For example, if they have a chunk of the system that allocates memory like crazy then goes away, they could allocate all their memory from aPool, then not even bother doing any deletes on the little pieces: just deallocate the entire pool at once.  Or they could set up a "shared memory" area (where the operating system specifically provides memory that is shared between multiple processes) and have the pool dole out chunks of shared memory rather than process-local memory.  Another angle: many systems support a non-standard function often called alloca() which allocates a block of memory from the stack rather than the heap.  Naturally this block of memory automatically goes away when the function returns, eliminating the need for explicitdeletes.  Someone could use alloca() to give thePool its big chunk of memory, then all the little pieces allocated from thatPool act like they're local: they automatically vanish when the function returns.  Of course the destructors don't get called in some of these cases, and if the destructors do something nontrivial you won't be able to use these techniques, but in cases where the destructor merely deallocates memory, these sorts of techniques can be useful.

Okay, assuming you survived the 6 or 8 lines of code needed to wrap your allocate function as a method of aPool class, the next step is to change the syntax for allocating objects.  The goal is to change from the rather clunky syntax new(pool.alloc(sizeof(Foo))) Foo() to thesimpler syntax new(pool) Foo() .  To make this happen, you need to add the following two lines of code just below the definition of yourPool class:

inline void* operator new(size_t nbytes, Pool& pool)
{
   return pool.alloc(nbytes);
}

Now when the compiler sees new(pool) Foo() , it calls the above operator new and passes sizeof(Foo) andpool as parameters, and the only function that ends up using the funky pool.alloc(nbytes) method is your own operator new .

Now to the issue of how to destruct/deallocate the Foo objects.  Recall thatthe brute force approach sometimes used with placementnew is to explicitly call the destructor then explicitly deallocate the memory:

void sample(Pool& pool)
{
   Foo* p = new(pool) Foo();
  
...
   p->~Foo();       
// explicitly call dtor
   pool.dealloc(p); 
// explicitly release the memory
}

This has several problems, all of which are fixable:

  1. The memory will leak if Foo::Foo() throws an exception.
  2. The destruction/deallocation syntax is different from what most programmers are used to, so they'll probably screw it up.
  3. Users must somehow remember which pool goes with which object. Since the code that allocates is often in a different function from the code that deallocates, programmers will have to pass around two pointers (a Foo* and a Pool* ), which gets ugly fast (example, what if they had an array ofFoos each of which potentially came from a differentPool; ugh).

We will fix them in the above order.

Problem #1: plugging the memory leak. When you use the "normal" new operator, e.g., Foo* p = new Foo() , the compiler generates some special code to handle the case when the constructor throws an exception.  The actual code generated by the compiler is functionally similar to this:

// This is functionally what happens with Foo* p = new Foo()

Foo* p;

// don't catch exceptions thrown by the allocator itself
void* raw = operator new(sizeof(Foo));

// catch any exceptions thrown by the ctor
try {
   p = new(raw) Foo(); 
// call the ctor with raw as this
}
catch (...) {
  
// oops, ctor threw an exception
   operator delete(raw);
   throw; 
// rethrow the ctor's exception
}

The point is that the compiler deallocates the memory if the ctor throws an exception.  But in the case of the "new with parameter" syntax (commonly called "placementnew"), the compiler won't know what to do if the exception occurs so by default it does nothing:

// This is functionally what happens with Foo* p = new(pool) Foo() :

void* raw = operator new(sizeof(Foo), pool);
// the above function simply returns "pool.alloc(sizeof(Foo))"

Foo* p = new(raw) Foo();
// if the above line "throws", pool.dealloc(raw) is NOT called

So the goal is to force the compiler to do something similar to what it does with the globalnew operator.  Fortunately it's simple: when the compiler sees new(pool) Foo() , it looks for a corresponding operator delete . If it finds one, it does the equivalent of wrapping the ctor call in atryblock as shown above.  So we would simply provide an operator delete with the following signature (be careful to get this right; if the second parameter has a different type from the second parameter of the operator new(size_t, Pool&) , the compiler doesn't complain; it simply bypasses thetry block when your users say new(pool) Foo() ):

void operator delete(void* p, Pool& pool)
{
   pool.dealloc(p);
}

After this, the compiler will automatically wrap the ctor calls of your newexpressions in atry block:

// This is functionally what happens with Foo* p = new(pool) Foo()

Foo* p;

// don't catch exceptions thrown by the allocator itself
void* raw = operator new(sizeof(Foo), pool);
// the above simply returns "pool.alloc(sizeof(Foo))"

// catch any exceptions thrown by the ctor
try {
   p = new(raw) Foo(); 
// call the ctor with raw as this
}
catch (...) {
  
// oops, ctor threw an exception
   operator delete(raw, pool); 
// that's the magical line!!
   throw; 
// rethrow the ctor's exception
}

In other words, the one-liner function operator delete(void* p, Pool& pool) causes the compiler to automagically plug the memory leak.  Of course that function can be, but doesn't have to be,inline.

Problems #2 ("ugly therefore error prone") and #3 ("users must manually associate pool-pointers with the object that allocated them, which is error prone") are solved simultaneously with an additional 10-20 lines of code in one place.  In other words, we add 10-20 lines of code in one place (your Pool header file) and simplify an arbitrarily large number of other places (every piece of code thatuses yourPool class).

The idea is to implicitly associate a Pool* with every allocation. The Pool* associated with the global allocator would beNULL, but at least conceptually you could sayevery allocation has an associated Pool* . Then you replace the global operator delete so it looks up the associated Pool* , and if non-NULL, calls thatPool's deallocate function.  For example,if(!) the normal deallocator used free() , the replacment for the global operator delete would look something like this:

void operator delete(void* p)
{
   if (p != NULL) {
     Pool* pool =
/* somehow get the associated 'Pool*' */ ;
     if (pool == NULL)
       free(p);
     else
       pool->dealloc(p);
   }
}

If you're not sure if the normal deallocator was free() , the easiest approach is also replace the global operator new with something that uses malloc() .  The replacement for the global operator new would look something like this (note: this definition ignores a few details such as thenew_handler loop and the throw std::bad_alloc() that happens if we run out of memory):

void* operator new(size_t nbytes)
{
   if (nbytes == 0)
     nbytes = 1; 
// so all alloc's get a distinct address
   void* raw = malloc(nbytes);
  
...somehow associate the NULL 'Pool*' with 'raw'...
   return raw;
}

The only remaining problem is to associate a Pool* with an allocation.  One approach, used in at least one commercial product, is to use a std::map<void*,Pool*> .  In other words, build a look-up table whose keys are the allocation-pointer and whose values are the associated Pool* . For reasons I'll describe in a moment, it is essential that you insert a key/value pair into the maponly in operator new(size_t,Pool&) .  In particular, you must not insert a key/value pair from the global operator new (e.g., you must not say, poolMap[p] = NULL in the global operator new ).  Reason: doing that would create a nasty chicken-and-egg problem — since std::map probably uses the global operator new , it ends up inserting a new entry every time inserts a new entry, leading to infinite recursion — bang you're dead.

Even though this technique requires a std::map look-up for each deallocation, it seems to have acceptable performance, at least in many cases.

Another approach that is faster but might use more memory and is a little trickier is to prepend a Pool* just beforeall allocations.  For example, ifnbytes was 24, meaning the caller was asking to allocate 24 bytes, we would allocate 28 (or 32 if you think the machine requires 8-byte alignment for things likedoubles and/or long long s), stuff the Pool* into the first 4 bytes, and return the pointer 4 (or 8) bytes from the beginning of what you allocated.  Then your global operator delete backs off the 4 (or 8) bytes, finds the Pool* , and if NULL, uses free() otherwise calls pool->dealloc() .  The parameter passed to free() and pool->dealloc() would be the pointer 4 (or 8) bytes to the left of the original parameter,p.  If(!) you decide on 4 byte alignment, your code would look something like this (although as before, the following operator new code elides the usual out-of-memory handlers):

void* operator new(size_t nbytes)
{
   if (nbytes == 0)
     nbytes = 1;                   
// so all alloc's get a distinct address
   void* ans = malloc(nbytes + 4); 
// overallocate by 4 bytes
   *(Pool**)ans = NULL;            
// use NULL in the globalnew
   return (char*)ans + 4;          
// don't let users see the Pool*
}

void* operator new(size_t nbytes, Pool& pool)
{
   if (nbytes == 0)
     nbytes = 1;                   
// so all alloc's get a distinct address
   void* ans = pool.alloc(nbytes + 4);
// overallocate by 4 bytes
   *(Pool**)ans = &pool;           
// put the Pool* here
   return (char*)ans + 4;          
// don't let users see the Pool*
}

void operator delete(void* p)
{
   if (p != NULL) {
     p = (char*)p - 4;             
// back off to the Pool*
     Pool* pool = *(Pool**)p;
     if (pool == NULL)
       free(p);                    
// note: 4 bytes left of the originalp
     else
       pool->dealloc(p);           
// note: 4 bytes left of the originalp
   }

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值