文章目录
1. C++非容器分配内存的途径
如下图所示, 当我们使用如下语句:
Foo* p = new Foo(x);
的时候, 默认走的是2
的路径, 也就是编译器会把上面这条语句翻译为:
Foo* p = (Foo*)operator new(sizeof(Fop));
new(p) Foo(x);
...
其中的operator new
默认走的是全局的::operator new(size_t);
函数, 最终调用malloc
来分配内存.
我们想要插手管理内存, 做法就是走1
的道路, 也就是在Foo
类中重载operator new
操作.
2. C++容器类分配内存的途径
容器类相比非容器类的内存分配, 多了一个"中间商", 也即标准库中的allocator
模板类.
如下图, 容器创建内存, 会先调用allocate
方法申请内存, allocate
方法里面则也是调用全局的::operator new
方法然后调用malloc
进行内存申请分配.
3. 重载全局operator new和operator delete
因为要重载全局的, 因此不能在某一个namespace中声明, 只能在全局中声明.
测试上图左边的代码, 我用了简单的测试程序:
void test05(){
int* p = new int[5]; //这里会调用全局operator new[]
delete[] p; //这里会调用全局operator delete[]
using sanjay02::A;
A* p_a = new A; //调用全局operator new
delete p_a; //调用全局operator delete
}
执行的结果是:
sanjay global new[]() #调用了我们重载的operator new[]
sanjay global delete[](ptr) # 同上delete[]
sanjay global new() # 调用重载的operator new
default ctor. this=0x55c5eaea5280 id=0 #构造函数
dtor. this=0x55c5eaea5280 id=0 #析构函数
sanajy global delete(ptr) # delete
4. 重载类内operator new/ operator delete(包括[])
4.1 要点
如下图, 当我们使用Foo* p = new Foo;
时, 编译器会转换为右边上面的代码, 当调用operator new
时, 会先去类中找是否有这样的函数.
当我们在类内重载operator new
的时候, 因为编译器是先调用operator new
这个方法的, 所以这个方法必须是静态的, 这样才能可以调用. 如果不是静态的, 在对象创建之前不能调用, 可是编译器要先用这个方法进行内存分配, 这样就矛盾了.
所以这个方法是static
的, 我们不写也可以, 因为C++编译器知道这两个必须为静态的, 因此默认是静态的了.
下面再来看看operator new[]的:
需要注意的点:
- 下图的右上, 当编译器使用
operator new
时, 申请的内存大小是sizeof(Foo) * N + 4
, 这个4
应该是cookie的大小 - 构造函数会调用N次
- 析构函数也会调用N次
4.2 测试例子
//----------------------测试类内operator new------------------------------
#include <cstddef>
#include <iostream>
#include <string>
namespace sanjay06
{
class Foo
{
public:
int _id; //4
long _data; //4
string _str; // 32 ==> 4 + 4 + 32 = 40 ==> 48 //64位 8字节对齐
public:
static void* operator new(size_t size);
static void operator delete(void* deadObject, size_t size);
static void* operator new[](size_t size);
static void operator delete[](void* deadObject, size_t size);
Foo() : _id(0) { cout << "default ctor. this=" << this << " id=" << _id << endl; }
Foo(int i) : _id(i) { cout << "ctor. this=" << this << " id=" << _id << endl; }
//virtual
~Foo() { cout << "dtor. this=" << this << " id=" << _id << endl; }
};
void* Foo::operator new(size_t size)
{
Foo* p = (Foo*)malloc(size);
cout << "Foo::operator new(), size=" << size << "\t return: " << p << endl;
return p;
}
void Foo::operator delete(void* pdead, size_t size)
{
cout << "Foo::operator delete(), pdead= " << pdead << " size= " << size << endl;
free(pdead);
}
void* Foo::operator new[](size_t size)
{
Foo* p = (Foo*)malloc(size); //crash, 問題可能出在這兒
cout << "Foo::operator new[](), size=" << size << "\t return: " << p << endl;
return p;
}
void Foo::operator delete[](void* pdead, size_t size)
{
cout << "Foo::operator delete[](), pdead= " << pdead << " size= " << size << endl;
free(pdead);
}
//-------------测试重载operator new 类内----------------------
void test_overload_operator_new_and_array_new()
{
cout << "\ntest_overload_operator_new_and_array_new().......... \n";
cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
{
Foo* p = new Foo(7);
delete p;
Foo* pArray = new Foo[5]; //无法给array new参数, 只能使用默认构造函数
delete [] pArray;
}
cout << "====================分割==========================" << endl;
{
cout << "testing global expression ::new and ::new[] \n";
// 這會繞過 overloaded new(), delete(), new[](), delete[]()
// 但當然 ctor, dtor 都會被正常呼叫.
Foo* p = ::new Foo(7);
::delete p;
Foo* pArray = ::new Foo[5];
::delete [] pArray;
}
}
} //namespace
运行的结果如下:
test_overload_operator_new_and_array_new()..........
sizeof(Foo)= 48
Foo::operator new(), size=48 return: 0x564256454280
ctor. this=0x564256454280 id=7
dtor. this=0x564256454280 id=7
Foo::operator delete(), pdead= 0x564256454280 size= 48
Foo::operator new[](), size=248 return: 0x5642564542c0
default ctor. this=0x5642564542c8 id=0
default ctor. this=0x5642564542f8 id=0
default ctor. this=0x564256454328 id=0
default ctor. this=0x564256454358 id=0
default ctor. this=0x564256454388 id=0
dtor. this=0x564256454388 id=0
dtor. this=0x564256454358 id=0
dtor. this=0x564256454328 id=0
dtor. this=0x5642564542f8 id=0
dtor. this=0x5642564542c8 id=0
Foo::operator delete[](), pdead= 0x5642564542c0 size= 248
====================分割==========================
testing global expression ::new and ::new[]
ctor. this=0x564256454280 id=7
dtor. this=0x564256454280 id=7
default ctor. this=0x5642564542c8 id=0
default ctor. this=0x5642564542f8 id=0
default ctor. this=0x564256454328 id=0
default ctor. this=0x564256454358 id=0
default ctor. this=0x564256454388 id=0
dtor. this=0x564256454388 id=0
dtor. this=0x564256454358 id=0
dtor. this=0x564256454328 id=0
dtor. this=0x5642564542f8 id=0
dtor. this=0x5642564542c8 id=0
分析:
- 由于我的电脑是64位的, 因为内存的对齐是8字节的
- 我使用
sizeof(string)
查看了大小, 发现string的大小是32
字节 - 因此Foo类的大小: 4 + 4 + 32 = 40, 然后字节对齐, 最终Foo类的大小就是48字节大小了. 这个和
sizeof(Foo)= 48
输出一致 - 当执行
Foo* p = new Foo(7);
时, 输出为
说明先调用我们重载的operator new函数, 然后再调用构造函数Foo::operator new(), size=48 return: 0x564256454280 ctor. this=0x564256454280 id=7
- 接着delete也符合
接下来是调用operator new[]的分析:
-
执行
Foo* pArray = new Foo[5];
, 由于array new无法给定初值, 因此只能调用默认构造函数. 这个语句的输出为:Foo::operator new[](), size=248 return: 0x5642564542c0 default ctor. this=0x5642564542c8 id=0 default ctor. this=0x5642564542f8 id=0 default ctor. this=0x564256454328 id=0 default ctor. this=0x564256454358 id=0 default ctor. this=0x564256454388 id=0
其中
size=248
的由来: 48 * 5 = 240, 然后再加8的cookie , 便等于248了. 然后调用5次构造函数. -
array delete同理.
当Foo类的析构函数加了virtual
之后, 这个类会变大, 变成了sizeof(Foo)= 56
, 变大了8个字节, 这是因为加了virtual之后, 需要增加虚函数表来维护, 因为会增大类的大小.