C++ 内存管理(一)----new/delete

一、C++程序使用memory的途径

在这里插入图片描述
一般来说user使用时都是调用标准库的std::allocator去分配内存,
而std::allocator内部是调用到了new,new[],new()【此为placement new】,::operator new()…,
而new…这些底层则又是调用了malloc / free。
(当然user也可直接调用new…或malloc / free或甚至更底层的东西。)

memory分配与释放的四个层面:
在这里插入图片描述
四个层面代码使用例子:

  • 旧版本
void* p1 = malloc(512);  // 512 bytes
free(p1);

complex<int>* p2 = new complex<int>;  // one object,实际上此时已经调用了系统默认构造函数而创建出对象了
delete p2;	 

void* p3 = ::operator new(512);  // 512 bytes
::operator delete(p3);

// 使用C++标准库提供的allocators
// 各厂商的实现形式如下:
#ifdef _MSC_VER
	// 以下两个函数是non-static,所以要通过object调用
	int* p4 = allocator<int>().allocate(3, (int*)0);  // 分配3个单元,第二参数不重要
	allocator<int>().deallocate(p4, 3);
#endif

#ifdef __BORLANDC__
	// 以下两个函数是non-static,所以要通过object调用
	int* p4 = allocator<int>().allocate(5);  // 分配5个单元,此时第二参数系统已给默认值所以不用指定
	allocator<int>().deallocate(p4, 3);
#endif

#ifdef __GNUC__
	// 以下两个函数是static,可通过全名调用
	void* p4 = alloc::allocate(512);  // 512 bytes
	alloc::deallocate(p4, 512);
#endif
  • 旧版本
void* p1 = malloc(512);  // 512 bytes
free(p1);

complex<int>* p2 = new complex<int>;  // one object,实际上此时已经调用了系统默认构造函数而创建出对象了
delete p2;	 

void* p3 = ::operator new(512);  // 512 bytes
::operator delete(p3);


#ifdef __GNUC__
	// 以下两个函数是non-static,所以要通过object调用
	int* p5 = allocator<int>().allocate(5);  // 分配5个单元,此时第二参数系统已给默认值所以不用指定
	allocator<int>().deallocate((int*)p4, 5);

	// 以下两个函数是non-static,所以要通过object调用
	void* p5 = __gnu_cxx::pool_alloc<int>().allocate(9);
	__gnu_cxx::pool_alloc<int>().deallocate((int*)p5, 9);
#endif

对比:新版本gnuc下,旧版本中好用的alloc已经变为__gnu_cxx::pool_alloc<int>(),且需通过对象调用!

二、new/delete expression( new/delete表达式 )

// 这里new创建返回的是一个类对象,并调用默认构造函数
Complex<int> a;
a = new Complex<int>()

// 这里new创建返回的是指向当前所创建的一个类对象的地址,调用重写的传参版本的构造函数
Complex* pc = new Complex<int>(1, 2)

// 这里new创建返回的是指向当前所创建的一个类对象的地址,并调用默认构造函数
Complex* pc = new Complex<int>

// 注意operator new()返回的是 void* 类型
void* operator new(size_t size, const std::nothrow_t&) _THROW0(){ // 第二个参数告诉编译器这个operator new()调用失败时也不hi抛出异常!
	void* p;
	while((p == malloc(size)) == 0){ // 这里已经调用malloc(size)分配好空间并赋值给p了,但如果内存空间不够时,则进入while循环,而不是return(p)
		_TRY_BEHIN 
			if(_callnewh(size) == 0) // 此时调用自己写的_callnewh()去释放一些无用或意义不大的占用空间,
				break;				 // 直到释放后的空间再次进入while条件执行malloc(size)并满足要求时,则跳出while并返回p
		__CATCH(std::bad_alloc) 
			return(0);
		__CATCH_END
	}
	return(p);
}
void __cadecl operator delete(void* p) _THROW0(){
	free(p);
}


Complex* pc = new Complex(1, 2);
...
delete pc;

1.编译器将上面的Complex* pc = new Complex(1, 2)在编译时转为:

Complex* pc;
try{
void* men = operator new(sizeof(Complex)); // 1.allocate,调用上面的operator new()
pc = static_cast<Complex*>(men); // 2.cast,将指针转为Complex*类型并赋值给p
pc->Complex::Complex(1, 2); // 3.construct,调用有传入参数版本的构造函数(若是Complex* pc = new Complex,则调用的是默认构造函数)
}
catch(std::bad_alloc){
// 若allocation失败就不执行constructor
}
/*
另外上面 try 中的第三步动作,只有编译器在编译操作时才可以直接调用ctor
我们若想直接调用ctor,则应该用placement new
new( p ) Complex(1, 2);
*/

2.编译器将上面的delete pc在编译时转为:

pc->~Complex(); // 先dtor,我们也可以直接调用dtor函数
operator delete(pc); // 然后释放内存,调用的是上面的operator delete()

注意区分newoperator new()new(),第一个是new表达式,第二个是重载函数,第三个是placement new

三、直接调用Ctor&Dtor

string* pstr = new string;
cout << "str=" << *pstr << endl;

pstr->string::string("jjhou"); //[Error] 'class std::basic_string<char>' has no member named 'string'
pstr->~string();  // 编译可以通过,但在执行时会出问题因为前面的调用构造函数不成功,所以无从析构,
				  // 即没有构造函数的成功实现,又何来析构函数的调用!
class A{
public:
	int id;
	A(int i): id{
		cout << "ctor.this=" << this << "id=" << id << endl;
	}
	~A(){
		cout << "dtor.this=" << this << endl;
	}
}A* pA = new A(1);    // ctor.this=000307A8 id=1
cout << pA->id << endl; // 1
pA->A::A(3);  // in VC6: ctor.this=000307A8 id=3
			  // in GCC: [Error] cannot call constructor 'jj02::A::A' directly
A::A(5);	  // 此为通过类名调用构造函数且创建的是临时对象,另外其地址也不再是000307A8,而是内存中的新地址
			  // in VC6: ctor.this=0013FFG0 id=5
			  // in GCC: [Error] cannot call constructor 'jj02::A::A' directly
			  // 		 因为是创建的是临时对象,因而会调用析构函数,但会报警告:
			  // 		 [Note] for a function-style cast, rmove the redundant '::A'
cout << pA->id << endl; // in VC6: 3
						// in GCC: 1
delete pA;				// dtor.this=000307A8

四、array new, array delete

- 用法实例:

Complex* pca = new Complex[3];  // 唤起3次默认构造函数ctor,而不是传参的构造函数
...
delete[] pac;  // 唤起3次dtor

string* psa = new string[3];
...
delete pas; // 只唤起1次dtor!
  • 若是没带指针的类如上面的class Complex,则在new出1个Complex[]数组并放置3个对象后,在delete时指调用1次或3次dtor都没问题,因为其不带指针,其delete时系统回收带cookie的那块内存地址同时连其中所存放的三个Complex object的内容也被回收,所以其内部的Complex object是否被调用1次或3次dtor都无所谓了:
    在这里插入图片描述
  • 若是带指针的类如上面的class string,则在则在new出1个String[]数组并放置3个对象后,其psa指针指向的地址空间存放有str1,str2,str3三个指针,且这三个指针又指向外面内存的地址。所以此时若只是delete psa,则系统回收时虽会将带cookie的那块内存地址回收,且调用了一次dtor函数释放了str3指向的外部的内存空间。但此时记录指向外部内存空间的str1和str2随着存放cookie的那块内存空间被释放而被删除掉了,由此便会造成原本由str1和str2指向的外部内存空间泄漏【并不是带cookie的那块内存地址泄漏】。
    在这里插入图片描述
class A{
public:
	int id;
	A(): id(0){ // 必须写default ctor以便new数组A[]时可以调用
		cout << "default ctor.this=" << this << "id=" << id << endl;
	}
	A(int i): id(i){
		cout << "ctor.this=" << this << "id=" << id << endl;
	}
	~A(){
		cout << "dtor.this=" << this << "id=" << id << endl;
	}
};

A* buf = new A[3];  // default ctor 3 次 [0]先于[1]先于[2]
					// A必须有default ctor,否则[Error] no matching function for call to 'A::A()'
					// 因为new数组的A[3]时,根本无法传入参数去调用传参的ctor,所以必须写个默认的ctor!!
A* tmp = buf;
cout << "buf=" << buf << "tmp=" << tmp << endl;
for(int i = 0; i < size; ++i)
	new(tmp++) A(i);   // ctor 3次,placement new是在已创建存在的内存地址空间插入元素
cout << "buf=" << buf << "tmp=" << tmp << endl;
delete[] buf;  // dtor 3次 [2]先于[1]先于[0]

输出结果如图:
在这里插入图片描述

- array size 在内存块中:

数组内部存放的数据是简单型的基本类型的元素时
在这里插入图片描述运用关键字new创建出来的对象是分配到堆(heap)的内存中的,且用pi去接受指向堆内存的这块地址的存放元素的起始位置。
而在这个int型例子中内存地址存放的是不带指针且是基本类型的数据时,所以释放时加不加[ ] 都不会产生内存泄漏。
int这种简单类型是没有构造或析构函数的,所以析构10次或析构1次都无影响!

数组内部存放元素是一个复合的类元素时
在这里插入图片描述此时数组内部存放的是复合类型的类元素【虽然也不带指针】,但如果此时只是delete p,则编译器会将p看成指向一块整块的内存空间,试图去解析这块内存空间的布局时遇到3无法解析则整个布局会乱掉。而使用delete[] p 时,则可以解析3的信息意义。

五、placement new

  • placement new是将object创建在allocated memory中(即已经创建好的内存中)
  • placement new根本没分配memory,所以一般无所谓的placement delete。但基于后面会重写placement new(),所以也必须写placement delete【实际上是operator delete,只是形式上称为placement delete】。
  • 其表述形式为:new( p)::operator new(size, void*)即有第二传入参数,且为指针类型
#include <new>

void* operator new(size_t, void* loc){
	return loc;
}

char* buf = new char[sizeof(Complex) * 3];
Complex* pc = new(buf)Complex(1, 2);
...
delete[] buf;

第一行的new char[sizeof(Complex) * 3];创建出可以存放3个Complex类大小的内存空间:
在这里插入图片描述
第二行的new(buf)Complex(1, 2)实际上编译器在编译时转化为:

Complex* pc;
try{
void* men = operator new(sizeof(Complex), buf); // 调用传入第二参数为指针类型的operator new(size_t, void* loc)
pc = static_cast<Complex*> (men);
pc->Complex::Complex(1, 2);
}
catch(std::bad_alloc){
// 若allocation失败则不执行constructor
}

而这里的operator new(size_t, void* loc)的内部操作为传入什么指针就传出什么指针的void类型,并没有实际进行分配内存的操作,则在空间足够时,就用接收到的返回指针去调用构造函数,从而将创建出来的对象放在原先已new好的内存空间地址。

另外,placement new其 ( ) 中间不一定只是存放指针来表示将元素存放进该指针指向的内存空间地址,通过重载placement new,我们还可以放其他参数或者多个参数如图
在这里插入图片描述

在这里插入图片描述在这里插入图片描述上述例子placement new的 ( ) 里面存放的不是指针元素,所以不再是将创建的元素对象放入原本已经括号内的指针所指向的地址的内存空间的操作了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值