At first glance, it's easy to allocate memory dynamically in C + +: New is assigned, delete is free, that's simple. However, this article is more complex and takes into account the customization hierarchy. This may not be important for simple programs, but it is necessary for you to control memory in your code, whether you can write a custom allocator, some kind of advanced memory management table, or a specific garbage collection mechanism.
This article is not a comprehensive manual, but an overview of the various memory allocation methods in C + +. It is intended for readers who are already familiar with the C + + language.
Native operator New
Let's start with the original operator new. Consider the following code, which allocates 5 int-type spaces and returns pointers to them [1]:
int* v = static_cast<int*> (:: Operator new (5 * sizeof (*V)));
When like the call on, operator new plays the native memory allocation role, similar to the malloc. The above is equivalent to:
int* v = static_cast<int*> (malloc (5 * sizeof (*V)));
Frees the memory allocated with operator new with operator Delete:
:: operator Delete (v);
Would you like to always use the native new and delete functions? Yes, only in the rare few, I will argue in the following article. Why use them instead of the original authentic malloc and free? A very good reason is that you want to keep the code in the C + + domain integrity. Mixing new and free (or malloc and delete) is undesirable (big no). Another reason to use new and delete is that you can overload (overload) or rewrite (override) These functions as long as you need them. Here's an example:
Copy Code code as follows:
void* operator new (size_t sz) throw (Std::bad_alloc)
{
Cerr << "Allocating" << sz << "BYTESN";
void* mem = malloc (SZ);
if (MEM)
return mem;
Else
Throw Std::bad_alloc ();
}
void operator delete (void* ptr) throw ()
{
Cerr << "deallocating at" << ptr << Endl;
Free (PTR);
}
In general, notice that new is used for the built-in type, the object of the class that does not contain the user's custom new function, and any type of array allocation space, using the global operator new. When new is used to instantiate a class that has been redefined new, the new function of that class is used.
Here's a look at the class with the new function.
operator new for a particular class
People are sometimes curious about the difference between "operator new" and "New Operator". The former can be an overloaded operator new, global or specific class or native operator new. The latter is the C + + built-in new operator that you often use to allocate memory, just like this:
car* mycar = new Car;
C + + supports operator overloading, and one of the things we can overload is new.
Here's an example:
Copy Code code as follows:
Class Base
{
Public
void* operator new (size_t SZ)
{
Cerr << "new" << sz << "BYTESN";
Return:: operator new (SZ);
}
void operator Delete (void* p)
{
Cerr << "Deleten";
:: operator delete (p);
}
Private
int m_data;
};
Class Derived:public Base
{
Private
int m_derived_data;
Vector<int> z, y, X, W;
};
int main ()
{
base* B = new Base;
Delete B;
derived* d = new Derived;
Delete D;
return 0;
}
Print Results:
New 4 bytes
Delete
New bytes
Delete
In the base class the overloaded operator new and operator delete are also inherited by the quilt class. As you can see, operator new has the correct size of two classes. Note the actual allocation of memory using:: operator new, which is described earlier in the original new. It is critical to call the preceding two colons to avoid infinite recursion (without which the function will always call itself down).
Why do you overload operator new for a class? There are many reasons for this.
performance: The default memory allocation operator is designed to be generic. Sometimes you want to assign to a very special object, and you can significantly improve memory management by customizing the allocation method. The situation is discussed in many books and articles. In particular, the 4th chapter of the "Modern C + + design" shows a very well designed for smaller objects and implements a custom assignment operator.
Debugging & Statistics: fully mastering the allocation and release of memory provides good flexibility for debugging, statistical information and performance analysis. You can insert your assignment operator into a guard that is designed to detect buffer overflows, detect memory leaks by comparing the allocation operator with the release operator (deallocations), and accumulate indicators for statistical and performance analysis, among other things.
Personalization: non-standard memory allocation methods. A good example is the memory pool or arenas, which makes memory management simpler. Another example is a perfect garbage collection system for an object that can write your own operators new and delete for a class or an entire layer.
It is helpful to study the new operator in C + +. The assignment is done in two steps:
1. First, use global operator new to instruct the system to request native memory.
2. Once the request memory is allocated, a new object begins to construct in it.
The C + + FAQ gives a good example and I would love to come out here:
When you write down this code:
foo* p = new Foo ();
The compiler generates code that resembles this functionality:
Copy Code code as follows:
foo* p;
Don ' t catch exceptions thrown by the allocator itself
Do not catch exceptions thrown by the allocator themselves
void* raw = operator new (sizeof (Foo));
Catch any exceptions thrown by the ctor
Catch any exceptions thrown by ctor
try {
p = new (Raw) Foo (); Call the ctor and raw as this allocates memory with raw calls like this ctor
}
catch (...) {
Oops, ctor threw an exception oh, ctor throws an exception
operator delete (raw);
Throw Rethrow the ctor ' s exception throw back the ctor exception
}
One of the interesting grammars in the try is called "Placement new," which we'll discuss shortly. To make the discussion complete, let's look at a similar situation when we use Delete to release an object, which is also done in two steps:
1. First, the destructor of the object to be deleted is invoked.
2. The memory consumed by the object is then returned to the system via the global operator delete function.
So:
Delete p;
equivalent to [2]:
if (P!= NULL) {
P->~foo ();
operator delete (p);
}
This is the right time for me to repeat the first paragraph of this article, if a class has its own operator new or operator delete, these functions will be invoked instead of calling global functions to allocate and reclaim memory.
Placement NEW
Now, back here we see the "placement new" problem in the sample code. It happens to really work with syntax in C + + code. First, I want to explain briefly how it works. Then we'll see when it's useful.
Calling placement new immediately skips the first step of object assignment. That means we don't request memory from the operating system. Instead, it tells it that there is a piece of memory used to construct the object [3]. The following code shows this:
Copy Code code as follows:
int main (int argc, const char* argv[])
{
A "normal" allocation. Asks the OS for memory, so we
Don ' t actually know where this ends up pointing.
A normal allocation. Request memory to the operating system so we don't know where it is pointing
int* iptr = new int;
Cerr << "Addr of iptr =" << iptr << Endl;
Create a buffer large enough to hold an integer, and
Note it address.
To create a large enough buffer to hold an integral type, note its address
char mem[sizeof (int)];
Cerr << "Addr of mem =" << (void*) mem << Endl;
Construct the new integer inside the buffer ' mem '.
The address is going to be mem ' s.
Constructs a new integer in the buffer mem, the address becomes the address of the MEM
int* iptr2 = new (mem) int;
Cerr << "Addr of iptr2 =" << iptr2 << Endl;
return 0;
}
The output on my machine is as follows:
Addr of iptr = 0x8679008
Addr of mem = 0xbfdd73d8
Addr of iptr2 = 0xbfdd73d8
As you can see, the structure of placement new is very simple. And the interesting question is, why do I need this stuff? The following shows that placement new is really useful in some scenarios:
· Custom non-intrusive memory management. When overloading operator new for a class also allows custom memory management, the key concept here is non-intrusive. Overloading a class with operator new requires you to change the source code of a class. But suppose we have a class code that doesn't or can't be changed. How can we still control the distribution of it? Placement new is the answer. This general-purpose programming technique, used by placement new, is called a memory pool, sometimes called arenas[4.
· In some programs, allocating objects in a specified memory area is necessary. One example is shared memory. Another example is an embedded program or a peripheral driver that uses a memory map, which can easily allocate objects in their "territory."
· Many container libraries are preconfigured with a large chunk of memory space. When an object is added, they must be constructed here, so they use the placement new. A typical example is the standard vector container.
Delete an object assigned with placement new
A C + + proverb is that an object created with new should be released with the delete. Does this apply equally to placement new? Not exactly:
Copy Code code as follows:
int main (int argc, const char* argv[])
{
char mem[sizeof (int)];
int* iptr2 = new (mem) int;
Delete iptr2; Whoops, segmentation fault! Wow, it's a mistake!
return 0;
}
To understand why the above code fragment deletes IPTR2 causes a segment error (or some kind of memory exception, which varies depending on the operating system), let's recall what the delete iptr2 actually did:
1. The destructor of the object that ' s being deleted is called.
First, invoke the destructor of the object that will be deleted.
2. Then, the memory occupied by the ' object is ' returned to ' OS, represented by the global operator delete function.
The memory occupied by this object in the operating system is then retracted with the global operator delete function.
The first step is no problem for objects assigned with placement new, but the second step is questionable. It is wrong to try to free up a memory that is not actually allocated by an assignment operator, but the code above does. Iptr2 points to a segment of a stack that is not allocated with global operator new. However, the delete iptr2 will attempt to free memory with the global operator delete. Of course it will be wrong.
So what should we do? How should we delete iptr2 correctly? Of course, we certainly don't think the compiler solves how to translate memory, after all, we just pass a pointer to placement new, which may be taken from the stack, from the memory pool or elsewhere. Therefore, it must be released manually according to the actual situation.
In fact, the placement new usage above is just a special case of new C + + that specifies the generalized placement new syntax for additional parameters. It is defined in the standard header file as follows:
Copy Code code as follows:
Inline void* operator new (std::size_t, void* __p) throw ()
{
return __p;
}
C + + A corresponding delete with the same parameters is also found, which is used to release an object. It is defined in the header file as follows:
Copy Code code as follows:
inline void operator Delete (void*, void*) throw ()
{
}
Indeed, the C + + run does not know how to release an object, so the delete function does not operate.
How to deconstruct it? For an int, you don't really need a destructor, but suppose the code is like this:
Char mem[sizeof (Foo)];
foo* fooptr = new (MEM) Foo;
For a meaningful class Foo. Once we don't need to fooptr, how should we deconstruct it? We must explicitly call its destructor:
Fooptr->~foo ();
Yes, explicit invocation of destructors is legal in C + +, and this is the only correct approach [5].
Conclusion
This is a complex topic, and this article only plays a role in the introduction of C + +, a variety of memory allocation method gives a "taste". Once you have studied some of the details you will find that there are many interesting programming techniques (for example, implementing a memory pool allocation). These issues are best presented in context, not as part of an ordinary introductory article. If you want to know more, consult the resource list below.
Resources
· C + + FAQ Lite, especially items 11.14 and 16.9
· "The C + + programming Language, 3rd Edition" by Bjarne stroustrup–10.4.11
· "Effective C + +, 3rd edition" by Scott Myers–item 52
· "Modern C + + Design" by Andrei Alexandrescu–chapter 4
· Several StackOverflow discussions. Start with this one and browse as long as your patience lasts.
|
I will still explicitly write in front of operator new :: (double colon), although this is not necessary. With all due respect, this is a good practice, especially when it is possible to avoid ambiguity in the class of overloaded operator new. |
[2] |
Note that this is checked for NULL. Doing so makes delete p safe, even if p is NULL. |
[3] |
Make sure you have enough memory allocated to the object and make sure that it is properly aligned, as you should do with pointers to placement new. |
[4] |
The memory pool itself is a big and fascinating topic. I'm not going to expand here, so I encourage you to get some information on the Internet, and thewiki is a good place as usual (good start). |
[5] |
In fact, the standard vector container uses this method to deconstruct the data it holds. |