C++ 内存管理
C++ 内存管理
C++ 内存管理接口层次
从上到下越接近操作系统
各中内存管理接口的属性
分配 | 释放 | 类属 | 可否重载 |
---|---|---|---|
malloc() | free() | C函数 | 不可 |
new | delete | C++表达式 | 不可 |
::operator new() | ::operator delete() | C++ 函数 | 可 |
allocator::allocate() | allocator::deallocate() | C++标准库 | 可自由设计来搭配容器使用 |
new 的说明
当调用 new的时候 事实上调用的时 ::operator new()
比如 int* pi = new int;
编译器事实上会执行时的时 pi = static_cast<int*>(::operator new(sizeof(int)));
这里的 operator new 是个全局函数,可以被重载,如果不去重载它,则编译器会调用默认的operator new 函数
operator new 的函数定义逻辑如下:
void* operator new(size_t size, const std::nothrow_t&) _THROW0()
{
void*p;
while ((p = malloc(size)) == 0)
{
// buy more memory or return null pointer
if (_callnewh(size) == 0) break;
_CATCH(std::bad_alloc) return (0);
_CATCH_END
}
return(p);
}
如果malloc 成功,则p != 0 return p, 返回成功分配的地址
如果malloc 不成功, 则 p==0 调函数_callnewh函数,
- 失败:未安装新的处理程序或新的处理程序不是活动的。(未设置new_handler, 此时抛出std::bad_alloc异常,返回0)
- 成功:调用注册过的new_handler函数,从新进行malloc。
代码测试
正常情况下的new表达式
#include <iostream>
using namespace std;
int main(void) {
while (1) {
try{
int *p = new int[100000];
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
break;
}
}
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果:
分析: 因为不断的调用了new, 也就是::operator new, 最终导致堆空间不足,所以最终抛出了std::bad_alloc异常。
设置new_handler但不抛异常
#include <iostream>
using namespace std;
void new_handler() {
cout <<"Aha" << endl;
}
int main(void) {
set_new_handler(::new_handler);
while (1) {
try{
int *p = new int[100000];
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
break;
}
}
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果
分析:
当内存不够用时,_callnewh调用了 注册过的 ::new_handler,所以_callnewh返回值为1,但::new_handler 并没有做什么内存释放的工作,没有产生空闲的堆空间,但却不抛出异常,此时::operator new 就陷入了死循环,注册过的::new_handler会一直被调用。 PS new_handler的作用时为了在内存不够时,在这个函数中清理一些内存来供应新开辟的需求。
设置new_handler 并抛出异常
#include <iostream>
using namespace std;
void new_handler() {
cout << "I can do nothing for you except throw a bad_alloc" << endl;
throw std::bad_alloc();
}
int main(void) {
set_new_handler(::new_handler);
while (1) {
try {
int *p = new int[100000];
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
break;
}
}
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果:
分析:
嗯,, 这个应该很好理解。
如果想不抛异常呢
int *p = new(std::nothrow)[size];
这样 如果 分配不到内存,则返回一个NULL(0), 剋通过p 是否 为 NULL 来判断是否分配成功。
重载operator new 函数
#include <iostream>
using namespace std;
inline void * operator new(size_t size) {
void* p = malloc(size);
if (p == NULL)
throw std::bad_alloc();
cout << "(size_t size): " << " malloc : addr = " << p << " size = " << size << endl;
return p;
}
int main(void) {
while(1) {
try{
int *pi = new int[10000000];
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
break;
}
}
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果
分析:
当 调用 new 时调用了 重载的 void* operator new (size_t size); 方法,当malloc 不能再分配时p == NULL,抛出std::bad_alloc 异常。
重载operator new[] 函数
#include <iostream>
using namespace std;
inline void * operator new(size_t size) {
void* p = malloc(size);
if (p == NULL)
throw std::bad_alloc();
cout << "(size_t size): " << " malloc : addr = " << p << " size = " << size << endl;
return p;
}
inline void* operator new[](size_t size) {
void* p = malloc(size);
cout << "[](size_t size): " << " malloc : addr = " << p << " size = " << size << endl;
return p;
}
struct Complex {
double real;
double image;
public:
Complex(double real = 0, double image = 0) :
real(real), image(image)
{}
//~Complex() {
// cout << "del" << endl;
//}
};
int main(void) {
try{
int*p = new int;
Complex *pi = new Complex[2];
*p = 3;
new(p)int(4);
cout << *p << endl;
delete[]pi;
}
catch (std::bad_alloc& e) {
cout << e.what() << endl;
}
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果
分析
这里的new ( p) int(4) 这种形式叫做placement new, 是通过一个指针调用构造函数, new§ 事实上的函数形式是 operator new(size_t, void* ) 也可以被重载,编译器默认的处理方式就是 返回 指针本身。
这里重载了operator new[] 函数,所以在使用 new[] 时就不去调用 operator new函数而是调用 operator new[] 函数。这里一共new 了 2 个Complex 对象,一个Complex 由2个double 组成(16个字节), 2个一共32个字节。这里我故意注释掉了析构函数,因为如果一个对象有析构函数,那么在开辟对象数组时还需要再多开辟size_t个字节(x86: 4个字节, x64: 8个字节),以确保能正确的保存对象的个数,以正确的对每个对象调用析构函数。
取消析构函数的注释
#include <iostream>
using namespace std;
inline void * operator new(size_t size) {
void* p = malloc(size);
if (p == NULL)
throw std::bad_alloc();
cout << "(size_t size): " << " malloc : addr = " << p << " size = " << size << endl;
return p;
}
inline void* operator new[](size_t size) {
void* p = malloc(size);
cout << "[](size_t size): " << " malloc : addr = " << p << " size = " << size << endl;
return p;
}
struct Complex {
double real;
double image;
public:
Complex(double real = 0, double image = 0) :
real(real), image(image)
{}
~Complex() {
cout << "del" << endl;
}
};
int main(void) {
Complex *pi = new Complex[2];
cout << pi << endl;
cout << *(reinterpret_cast<size_t*>(pi) - 1) << endl;
delete[]pi;
cout << "Done" << endl;
#if defined(_WIN32)
system("pause");
#endif // defined(_WIN32)
return 0;
}
运行结果
分析
这里 malloc 的地址是…50 但最终pi 的地址是…58 ,因为返回时,编译器对地址进行了+8(编译器实现,重载时不需要实现) ,而这8个字节刚好存放的就是 2 ,对象的个数,再调用delete []时会检查高sizeof(size_t)位存放的数值,从高地址向低地址对对象调用析构函数。
同样的重载operator delete[](void* ptr) 时也存在编译器预先做好的工作,当重载 operator delete[](void* ptr) 时当每个对象的析构函数都已经调用完成后才会调用该operator delete[]函数。