C++ 内存管理

C++ 内存管理接口层次

从上到下越接近操作系统

Created with Raphaël 2.2.0C++ ApplicationsC++ Library(std::allocator)C++ primitives(new, new[],new(), ::operator new(), ....)CRT-C语言运行环境(malloc/free)OS API(such as HeapAlloc, VirtualAlloc,...)

各中内存管理接口的属性

分配 释放 类属 可否重载
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;
}

运行结果:
C++ 内存管理
分析: 因为不断的调用了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;
}

运行结果
C++ 内存管理
分析:
当内存不够用时,_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;
}

运行结果:
C++ 内存管理
分析:
嗯,, 这个应该很好理解。

如果想不抛异常呢

	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;
}

运行结果
C++ 内存管理
分析:
当 调用 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;
}

运行结果
C++ 内存管理
分析
这里的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;
}

运行结果
C++ 内存管理
分析
这里 malloc 的地址是…50 但最终pi 的地址是…58 ,因为返回时,编译器对地址进行了+8(编译器实现,重载时不需要实现) ,而这8个字节刚好存放的就是 2 ,对象的个数,再调用delete []时会检查高sizeof(size_t)位存放的数值,从高地址向低地址对对象调用析构函数。

同样的重载operator delete[](void* ptr) 时也存在编译器预先做好的工作,当重载 operator delete[](void* ptr) 时当每个对象的析构函数都已经调用完成后才会调用该operator delete[]函数。