C++初阶---内存管理

内存管理

引入(C部分回顾)

内核空间
属于操作系统范畴
:(向下)
函数调用建立栈帧,参数,函数中的局部变量都存在栈帧中
:(向上)
理论上而言,后malloc的内存地址比先malloc的大,但不一定,因为下一次申请的空间可能是之前释放的
数据段
通常用来存放程序中已初始化的(非 0)全局变量和静态局部变量。数据段的起始位置由链接定位文件确认,大小在编译链接时自动分配。数据段属于静态内存分配
代码段
代码段在内存中被映射为只读。它是由编译器在编译链接时自动计算的。通常是用来存放可执行代码(二进制指令)和只读常量,代码段输入静态内存分配


对于图中的这个区域划分
C++初阶---内存管理
以32位4G为例

  1. 假如内核占1G,剩下的3G,大部分都是堆
  2. 栈很小,linux下一般只有8M,所以当递归调用太深会出现栈溢出
  3. 数据段和代码段不是很大

注意
上图中的char_v字符数组是存储在栈上的,char_v是首元素地址解引用,为‘a’同样是在栈上(注意区分ptr_char_v和*char_v)

malloc/calloc/realloc区别
calloc等价于:malloc+memset(0)+初始化
realloc是对malloc或calloc的空间进行扩容


C++内存管理

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出自己的内存管理方式:通过newdelete 操作符进行动态内存管理


1)new delete

new delete 对于内置类型

实现原理
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL
用法

void test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
	// 动态申请10个int类型的空间
	int* ptr6 = new int[3];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

对比malloc/free
(对于内置类型new/delete,malloc/free没有什么区别)C++初阶---内存管理

new delete 对于自定义类型

实现原理

  1. new: 调用operator new函数申请空间,在申请的空间上执行构造函数,完成对象的构造
    delete: 在空间上执行析构函数,完成对象中资源的清理工作,调用operator delete函数释放对象的空间
  2. new[]: 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,在申请的空间上执行N次构造函数
    delete[]: 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

用法

class Test
{
public:
	Test()
		: _data(0)
	{
		cout << "Test():" << this << endl;
	}
	~Test()
	{
		cout << "~Test():" << this << endl;
	}
private:
	int _data;
};
//new/delete对于自定义
void Test2()
{
	// 申请单个Test类型的对象
	Test* p1 = new Test;
	delete p1;
	// 申请10个Test类型的对象
	Test* p2 = new Test[10];
	delete[] p2;
}

与malloc/free对比

  1. new/delete会调用自定义类型的构造和析构函数C++初阶---内存管理
  2. 而malloc/free不会调用
    可以看到一共只有new/delete调用的11对构造,析构函数
    C++初阶---内存管理

注意

  1. 一定要匹配(malloc对应freenew对应deletenew[]对应delete[])使用,否则可能会崩溃 (测试表明内置类型不会崩溃,而自定义类型会崩溃)
    C++初阶---内存管理
    C++初阶---内存管理
  2. 在C++,建议尽量使用new/delete

2)operator new与operator delete函数

malloc/free和new/delete对于空间申请失败的处理方式

引入 malloc/free和new/delete对于空间申请失败的处理方式不同:

对于malloc/free:

// malloc失败,返回NULL
char* p1 = (char*)malloc((size_t)2*1024*1024*1024);
if (p1 == NULL)
{
	printf("malloc fail\n");
}
else
{
	printf("malloc success:%p\n", p1);
}

C++初阶---内存管理


对于new/delete:

// new与malloc不一样,申请空间失败,抛异常
try
{
	char* p2 = new char[0x7fffffff]; // 出错,抛异常,跳到捕获异常的位置
	printf("xxxx");//不会执行之后的语句
}
catch (const exception& e)
{
	cout << e.what() << endl;
}

C++初阶---内存管理


operator new

源码如下:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
      {       // try to allocate size bytes
       void *p;
       while ((p = malloc(size)) == 0)
               if (_callnewh(size) == 0)
               {       // report no memory
              		   //内存申请失败,抛出bad_alloc异常
               static const std::bad_alloc nomem;
               _RAISE(nomem);
               }
       return (p);
       }

可以认为operator new函数是对malloc的封装,并且加上了抛异常


注意

  1. 这不是运算符重载
  2. 不是new,不会调用构造函数(new=operator new+构造函数
operator delete

源码如下:

void operator delete(void *pUserData)
	{
	 	_CrtMemBlockHeader * pHead;
	 	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
		if (pUserData == NULL)
			return;
	 	_mlock(_HEAP_LOCK); /* block other threads */
	 	__TRY
	 		/* get a pointer to memory block header */
	 		pHead = pHdr(pUserData);
	 		/* verify block type */
	 		_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
			//与free一样调用_free_dbg
	 		_free_dbg( pUserData, pHead->nBlockUse );
	 	__FINALLY
	 		_munlock(_HEAP_LOCK); /* release other threads */
	 	__END_TRY_FINALLY
	 	return;
	 }

Debug版本的 CRT定义了一套调试版本的内存分配函数(如_malloc_dbg)。当你包含了CRTDBG.h后,如果当前是Debug工程,十有八九会有_DEBUG宏,我们找到CRTDBG.h
C++初阶---内存管理
发现free同样是调用_free_dbg
注意

  1. 该函数最终是通过free来释放空间的
  2. 不是delete,不会调用析构函数(delete=析构函数+operator delete

operator new与operator delete的类专属重载

引入编写一个检测有没有ListNode的节点没有释放的程序并计数
部分代码(重载部分)如下:

	A(int a = 0)
	{
		cout << "A()" << this << endl;
	}
	void* operator new(size_t n)
	{
		_count++;
		return ::operator new(n);
	}
	void operator delete(void* p)
	{
		_count--;
		return ::operator delete(p);
	}
private:
	static int _count;
};
int A:: _count = 0;//static初始化

前面说过,new/delete是关键字也是运算符 这样重载new和delete后,就是相当于在用new/delete的时候调用运算符重载,相当于:调用operator new/operator delete函数,同时加上计数操作,最后打印_count即可


3)new/delete结合实际情况分析

class Stack
{
public:
	Stack(int capacity = 4)
		:_a(new int[capacity])
		, _size(0)
		, _capacity(capacity)
	{
		cout << "Stack(int capacity = 4)" << endl;
	}
	~Stack()
	{
		delete[] _a;
		_size = _capacity = 0;
		cout << "~Stack()" << endl;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
	Stack st;//语句1
	Stack* ps = new Stack;//语句2
	delete ps;
	return 0;
}

分析语句1,2


分析

  1. 语句1 Stack st在main函数栈帧里实例化一个对象st,调用他的构造函数初始化列表时new int[capacity] (new调用operator new,operator new调用malloc) 会在堆上开辟一块capicity个int的空间当出了作用域,自动调用析构函数在函数里delete[] _a会销毁堆上的空间(所谓的清理资源),出了main函数,st也被销毁
  2. 语句2 Stack* ps = new Stack;是在main函数栈帧上定义一个4字节的指针ps,同时new一个12字节的Stack类(由于是new,会在堆上开辟这12字节的空间),同时再去调用他的构造函数,new int[capacity]在堆上再开辟capicity个int的空间,将空间给_a,返回给ps指针。当我们要释放空间的时候,如果用free会只释放栈上的空间,所以用delete才能正确释放,先调用析构函数,在调用operator delete进行释放空间
    C++初阶---内存管理

4)定位new表达式placement-new

概念 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象


使用

new (place_address) 类
new (place_address) 类(initializer-list)
//place_address:一个指针
//initializer-list:类型的初始化列表

场景示例
先看下列代码:

A a[5];
A* pb1 = new A[5];// 复制一份a数组到另外一块空间b
for (int i = 0; i < 5; ++i)
{
	pb1[i] = a[i];
}

C++初阶---内存管理

// 直接拷贝构造
A* pb2 = (A*)malloc(sizeof(A) * 5);
for (int i = 0; i < 5; ++i)
{
	new(pb2 + i)A(a[i]);
}

C++初阶---内存管理


5)总结malloc/free new/delete作用

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
  3. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  4. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  5. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间
    后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

6)内存泄漏

上一篇:red5,或者说是开源软件之痛。


下一篇:软件开发工具化: 信息、观点、准则和工具