malloc、free
C语言中,我们使用malloc分配内存,使用free释放内存。下面代码我们申请了10个字节的空间,但实际上我们获得的空间会大于10个字节。
char* s = (char*)malloc(sizeof(char) * 10);
free(s);
s = NULL;
下面的结构体用来存储申请的内存块信息。
struct mem_control_block {
int is_available;
int size;
}
free根据size来释放空间,标记is_available为可用。
void free(void* p) {
struct mem_control_block* mcbp;
mcbp = p - sizeof(mem_control_block);
mcbp -> is_available = 1;
}
free函数对这一点的实现没有统一的标准,在不同的编译程序中,甚至同一编译程序的不同版本中,其实现都有可能不同。它可能会使用一张记录所分配内存地址和长度的表,或将有关数据存放在内存块,或用一个指针存储相关管理信息等等。 —— 《UNIX环境高级编程》
new、delete
C++语言中,我们使用new/new[]分配内存,使用delete/delete[]释放内存。
char* s = new char[10];
delete[] s;
s = nullptr;
int* i = new int(0);
delete i;
i = nullptr;
对于内置对象而言,new操作符会调用默认构造函数,对于自定义对象而言,new操作符会调用我们编写的构造函数,相比于malloc,new操作符多了初始化的操作。相应的delete操作符除了释放空间外,也会调用析构函数。
new操作符都干了什么事情?
string* sp = new string("hello world");
void* p = operator new(sizeof(string)); // 内部调用malloc分配内存
new(p) string("hello world"); // 在p上调用构造函数
string* sp = static_cast<string*>(p); // void* to string*
operator new不是操作符而是函数,内部也是使用malloc来分配内存,如果类中没有重载operator new函数,new操作符会缺省调用::operator new函数,这个全局operator new函数也可以被重载,这样影响的就是所有对象的内存分配。
void* operator new(size_t sz) { // new
void *p = malloc(sz);
return p;
}
void* operator new[](size_t sz) { // new[]
void *p = malloc(sz);
return p;
}
operator new函数的第一个参数必须是size_t,可以自定义后续参数,size_t由new操作符计算并自动传入。例如new int()
调用operator new中sz的值为sizeof(int)
。
但是new int[10]
调用operator new[]中sz的值并不是sizeof(int)*10
,会多分配4个字节用于存储长度。new[]操作符会根据这4个字节构造所有对象。
new操作符是先调用operator new分配内存,再调用构造函数;delete操作符则是先调用析构函数,再调用operator delete释放内存。
void operator delete(void* p) {
free(p);
}
void operator delete[](void* p) {
free(p);
}
delete[]操作符会根据多出来个4个字节来去析构所有的对象。
placement new
我们每次使用new去创建对象时,会自动寻找一块空闲内存,那么我们能不能只调用构造函数,不申请内存呢?这就需要placement new,在已经存在的合法空间上去构造对象。
需要注意的是,placement new并不是指有一个名为placement new的函数。
int* p = (int*)malloc(sizeof(int));
new(p) int(0);
上面的代码就是placement new,先创建一块内存,然后在这块内存上去调用构造函数。之所以可以new(p)
这样使用的原因是因为重载了operator new函数。
void* operator new(size_t, void* p) {
return p;
}
void* operator new[](size_t, void* p) {
return p;
}
上面的代码中,已经不需要size_t参数了,所以只做占位用。我们也可以继续添加其他参数,来实现更多的功能。
void* operator new(size_t, void* p, int i) {
return p + i;
}
于是我们可以像这样使用new操作符。
T* memo = (T*)malloc(sizeof(T)*10);
new(memo, 3) T();
上面的代码我们在长度为10的T数组中的第三个位置,构造了一个T对象。placement new使我们可以在一片空间上反复构造新的对象,省去了申请空间所需的时间,大大提高了程序的运行效率。