注意:这篇文章上次更新于1597天前,文章内容可能已经过时。
This article was last updated1597 days ago, the content may be outdated.
C++ 中的 new
C++ 中使用 new 关键字来创建一个对象,创建对象的过程会产生以下操作:
- 在内存中申请一块空间,大小为目标对象的大小,并获得该内存空间的地址。
- 将该内存空间的地址转换为对象指针。
- 通过对象指针调用该对象的构造函数。
new in C++
In C++, the new keyword is used to create an object, and the object creation process involves the following operations:
- Allocate a block of memory, sized to the target object, and obtain the address of that memory.
- Convert the address of that memory into an object pointer.
- Invoke the object’s constructor through the object pointer.
对于第一步来说,C++如何获得内存空间的呢?
答案是通过调用operator new(size_t size) 函数,其参数为要申请的空间大小,内部实现依然是通过 C 语言的 malloc 函数。值得一提的是,这个函数是可以进行重载的,这就为我们的内存管理留下了可操作空间。
For the first step, how does C++ obtain the memory?
The answer is by calling the operator new(size_t size) function, whose parameter is the size of the memory to allocate; internally it is still implemented through the C language’s malloc function. It is worth mentioning that this function can be overloaded, which leaves room for our own memory management.
默认情况下,new 表达式调用的是全局的 ::operator new(size_t size) 函数来申请内存空间,当我们在类中定义了成员函数 operator new(size_t size),编译器就会调用类中的成员operator new 函数来分配内存空间,所有内存管理的操作,也可以在成员operator new 函数中来实现。
By default, the new expression calls the global ::operator new(size_t size) function to allocate memory. When we define a member function operator new(size_t size) in a class, the compiler calls the class’s member operator new function to allocate memory, and all memory management operations can also be implemented in the member operator new function.
delete 与 new 同理,会先通过指针调用该对象的析构函数,再调用 operator delete 函数来释放内存空间,其中 operator delete 也分为全局函数和成员函数,也支持重载,全局 operator delete 函数内部使用 C 语言的 free 来释放内存空间。
用代码表示为:
delete works the same way as new: it first calls the object’s destructor through the pointer, then calls the operator delete function to release the memory. The operator delete function is also divided into a global function and a member function, and also supports overloading. The global operator delete function uses the C language’s free internally to release memory.
In code, this is:
1 | Foo* p = new Foo(x); // new 表达式,不可改变,不可重载 |
上面这一行代码会被转化为:
The line of code above is transformed into:
1 | Foo* p = (Foo*)operator new(sizeof(Foo)); // 调用 operator new 函数申请内存空间 |
我们所面临的问题是,当用户需要大量的创建 Foo 对象时,需要重复的反复的调用 malloc 函数来申请内存空间,这会带来两个问题:
- 调用 malloc 的次数太多,从而影响效率。(在 C++ 中申请和释放内存空间最终都是通过使用 C 语言的 malloc 和 free 函数来实现。其实 malloc 函数的执行速度并不慢,内存管理的目的也不是为了减少 malloc 的次数,当然,即使 malloc 函数的效率很高,减少它的调用次数也依然是好事情。)
- 浪费空间,C++为每个对象申请到的内存空间都会使用 cookie 来标志起始和结束位置,每个cookie各占用 4 个字节,如果你的对象数据成员很少的话,这将会是很大比例的浪费。
为了解决上面那两个问题,我们就需要定义成员operator new 和operator delete 函数,让 new 和 delete 的行为有我们自己控制。
The problem we face is that when users need to create a large number of Foo objects, they must repeatedly call malloc to allocate memory, which brings two problems:
- Too many calls to malloc, affecting efficiency. (In C++, allocating and freeing memory ultimately goes through the C language’s malloc and free functions. In fact, malloc is not slow in execution, and the purpose of memory management is not to reduce the number of malloc calls; of course, even if malloc is very efficient, reducing the number of calls is still a good thing.)
- Wasted space: the memory allocated for each object in C++ uses a cookie to mark the start and end positions, each cookie taking 4 bytes. If your object has very few data members, this can be a large proportion of waste.
To solve the two problems above, we need to define the member operator new and operator delete functions, so that the behavior of new and delete is under our own control.
内存管理的一些例子
第一个例子取材于 C++ Primer。
这个例子通过成员operator new 函数实现一次性申请足够大的空间(只有两个 cookie),在创建对象时将申请到的空间拿出一部分来构造对象。为了维护空间内的对象,为对象附加一个指针变量,使其形成链表的形式。这就是内存池的思想。

Some Examples of Memory Management
The first example is taken from C++ Primer.
This example uses the member operator new function to allocate a large enough block at once (with only two cookies), and when creating objects, takes a part of the allocated space to construct the objects. To maintain the objects within the space, a pointer variable is attached to each object so that they form a linked list. This is the idea of a memory pool.

1 |
|
这个版本的代码实现了基本的内存池,通过单向链表的方式维护了内存池,这种方式需要在类中专门定义一个用来维护内存池的指针,在本例中,用来维护内存池的数据成员和类原始数据成员一样大,显然一定程度上造成了空间的浪费。
下面的代码采用内嵌指针的方式解决上面代码中的问题。
This version of the code implements a basic memory pool, maintained through a singly linked list. This approach requires a dedicated pointer in the class to maintain the memory pool. In this example, the data member used to maintain the memory pool is as large as the class’s original data members, which obviously wastes some space to some extent.
The following code uses an embedded pointer approach to solve the problems in the code above.
1 | class Airplane{ |
在上面例子中,使用嵌入式指针的方式避免了额外的空间开销。
想象这样一种情况,使用者 new 了 10000 个对象,然后 delete 了 5000 个,又 new 了 6000 个,又 delete 10000 个…
这种情况下,每次释放的空间会被插入到链表的头部,反复这样操作会出现一个链表长度的峰值,即使实际对象数量小于链表的长度,使得链表中有很多空闲空间,这样的情况也不算是内存泄漏。
当然,如果这些空闲的空间能想办法还给操作系统就更好了。
In the example above, the embedded pointer approach avoids extra space overhead.
Imagine this situation: the user news 10000 objects, then deletes 5000, news 6000 again, and deletes 10000 again…
In this case, each freed space is inserted at the head of the linked list. Repeatedly doing this will produce a peak in the linked list length, even when the actual number of objects is smaller than the list length, leaving a lot of free space in the list. Such a situation does not count as a memory leak.
Of course, it would be even better if these free spaces could be returned to the operating system.
上面版本的内存管理操作已经很好了,但是对于不同的需要内存管理 class 都需要写一个这样的函数,为了避免过多的重复性的代码,我们把上述内存管理操作封装成类 allocator ,这样在需要内存管理的类中包含一个 allocator 对象即可。
The memory management approach above is already quite good, but every class that needs memory management has to write such a function. To avoid too much repetitive code, we encapsulate the memory management operations above into an allocator class, so that classes needing memory management only need to contain an allocator object.
1 | class allocator{ |
上面的设计比前两种的设计都干净多了,不再需要在分配内存的时候进行指针的类型的各种转型。
进一步可以发现,每个需要内存管理的类需要添加的内容依然很有规律性,它需要添加下面这部分代码:
The design above is much cleaner than the previous two; it no longer requires various pointer type casts when allocating memory.
Going further, the content that each class needing memory management must add is still quite regular; it needs to add the following part of code:
1 | public: |
于是为了更加的简洁,我们可以把上述代码写成宏的方式。
For even more conciseness, we can write the code above as macros.
1 |
|
上面所有的例子中利用一条链表来维护内存空间,将其发展为 16 条链表,即下图的形式,并且不再以 class 内的 static 成员出现,而是一个全局的 allocator,这就是 std::alloc 的雏形。

All the examples above use a single linked list to maintain memory. Developing this into 16 linked lists, in the form shown in the figure below, and no longer as a static member inside the class but as a global allocator, this is the prototype of std::alloc.



