c/c++内存管理

c/c++内存管理

​ c++new和delete操作符的出现,相较于malloc/free,使得堆上开辟的空间,也支持了自动调用构造和析构,可以实现初始化,操作也简单化,如:无需手动计算大小和强制类型,甚至连检查是否成功开辟空间也不用自己写。

1.认识操作系统中的进程地址空间

栈空间就几MB,堆空间大小约2GB;

在这里插入图片描述

Linux进程地址空间从低到高:

1.正文代码段分为,代码区和字符常量区,编写的代码存放于代码区,字符常量区存放常量字符串还有常量等;

2.全局数据段分为,已初始化全局区存放初始化不为零的全局数据或静态数据,未初始化全局区存放初始化为0或未初始化的全局数据或者静态数据;

3.堆区和栈区相对而生,堆向上生长,栈向下生长。堆用来开辟动态空间由用户调用系统调用开辟和释放,栈由操作系统管理,存放局部数据;

4.共享区用来进行如动态库的加载,共享内存的申请,使得多个进程看到同一份资源;

5.命令行参数和环境变量区主要是用于命令行解释器输入指令,main函数的命令行参数,以字符串形式来接收命令行传递的命令行参数向量表,在main函数内部进行指令带选项的设计。main函数的环境变量参数,通过接收环境变量向量表进行环境变量的传递,实现子进程继承父进程的环境变量;

6.用户在执行代码的时候进行身份切换,库和自己写的代码以用户态执行,在用户空间执行,而内核态是通过系统调用执行内核空间的代码;

2.const修饰

​ const修饰后不会改变变量存储位置;

int main()
{
    char ch[]="abc";//在栈上为char数组开辟空间,然后进行数组的初始化,即浅拷贝;
    const char* ptr="abc";//在栈上为指针变量开辟空间,然后对指针变量进行初始化,即指针变量的值等于字符串的地址;
}

3.c语言中动态内存的管理方式

3.1.malloc等四个函数得使用:

​ malloc(空间大小);calloc(初始化多少数量的类型,每个类型的大小);realloc(当前堆空间地址,要扩容成多大空间);free(对空间地址);

​ malloc实际开辟的空间要比要求开辟的空间要大一些,这部分空间叫做cookie用来存放和维护所开辟对空间的信息;

3.2.函数区别:

​ malloc没有初始化,calloc强制初始化成了0,realloc进行扩容,可能原地扩容也可能异地扩容;

3.3扩容方式

​ 原地扩容:若当前堆空间后所需要大小的连续空间没人使用,就申请过来,然后返回原空间首地址;异地扩容:新开辟一段堆空间将原空间的数据拷贝过来,再释放原空间,返回新空间首地址;对于之后不使用的指针变量要置空

4.c++动态内存管理方式

4.1new和delete的使用和特性

​ 1.c++使用了new和delete操作符搭配使用;

​ 2.new的返回值是指针;

​ 3.自定义类型new会先开辟空间,再调用构造,不传参调用默认构造,所以必须有默认构造函数

因为兼容c所以内置类型new先开辟空间,为了语法统一,支持显式初始化;

​ 4.自定义类型delete会先调用析构函数,再释放空间;内置类型直接释放空间;

​ 5.new和delete的底层调用顺序保障了申请空间和释放空间的安全,防止内存泄漏,如:堆空间申请栈对象;

​ 6.不需要对开辟空间进行检查,因为operator new会进行抛异常;

class A
{
public:
	A(int aa = 0, int bb = 0) :a(aa), b(bb) {}
	~A() {}

private:
	int a;
	int b;
};
int main()
{
	int* p1 = new int;//new一个int类型的空间,没初始化;
	delete p1;
	int* p2 = new int(1);//new一个int类型的空间,并初始化为1
	delete p2;
    int n = 3;
	int* p3 = new int[n]{1};//new 3个int类型的空间,并初始化为1
	delete[] p3;
    A* p4 = new A(1,1);//分解成两部分,先开辟空间,再调用构造
    delete p4;//两部分,先调用析构,再释放空间
	return 0;
}

在这里插入图片描述

4.2初始化

class A
{
public:
	A(int aa = 0, int bb = 0) :a(aa), b(bb) {}
	~A() {}

private:
	int a;
	int b;
};
int main()
{
    A a[2]{ {},{} };//全部初始化为缺省值
	A* ptr = new A[3]{ {1,2} ,{2,3},(0,3) };
	int a = (0, 3);
	return 0;
}

在这里插入图片描述

解释:

​ 1.单个变量只需要1个值初始化是用”()“或者“=”,而数组,自定义类型等需要多个值初始化是用“{}”

​ 2.(,)是逗号表达式,从左往右执行,返回结果是最有的值,所以a=3,A数组的第三个元素只传递了3;

​ 3.c++的变量在初始化之前是不会const修饰的;

4.3operator new operator delete 函数

new和delete是用户进行动态内存申请和释放的操作符operator new和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

​ operator new全局函数除了调用malloc,还进行了检查异常;operator delete先检查,调用 _free_dbg()函数,free(p)函数是一个宏函数,会转换成**_free_dbg(p,_NORMAL_BLOCK)函数**。即本质上malloc、free与operator new、operator delete是一样的;

面向对象的语言处理失败不喜欢用返回值,而是用抛异常

4.3.1new delete的底层实现

new:开空间+调用构造函数;开空间:使用operator new全局函数(支持抛异常);

在这里插入图片描述

delete:调用析构函数 + 释放空间;释放空间:operator delete;

在这里插入图片描述

new T[N] 的原理

1.调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

2.在申请的空间上执行N次构造函数

delete[] 的原理

1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放

4.4在堆上申请一个栈对象

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t 	  _size;
	size_t    _capacity;
};
int main()
{
    try
	{
		Stack* st = new Stack;//先new 开辟Stack堆对象,12字节,后构造函数开辟动态数组;
		delete st;//先释放动态数组,后释放堆对象
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

在这里插入图片描述

5.定位new表达式**(placement-new)**

5.1作用

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

​ 注意:要与显式调用析构函数搭配使用。

5.2使用场景

​ 1.定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

池化技术:申请空间,建立连接等操作需要访问系统内核,有时间成本,所以可以提前创建好一批对象使用,提高效率

​ 2.用malloc创建自定义类型或者operator new;

class A
{
public:
 A(int a = 0)
 : _a(a)
 {
 cout << "A():" << this << endl;
 }
 ~A()
 {
 cout << "~A():" << this << endl;
 }
private:
 int _a;
};
// 定位new/placement-new
int main()
{
    // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
    //有执行
    A* p1 = (A*)malloc(sizeof(A));
    new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
    p1->~A();
    free(p1);
    A* p2 = (A*)operator new(sizeof(A));
    new(p2)A(10);
    p2->~A();
    operator delete(p2);
    return 0;
}

6. malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地

方是:

1.malloc和free是函数,new和delete是操作符 ;

2.malloc申请的空间不会初始化,new可以初始化 ;

3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可 ;

4.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型 ;

5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常 ;

6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理 ;

7.内存泄漏

补充:cout识别char*为字符串;

7.1内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏 (Heap leak)

​ 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

​ 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

7.2内存泄露解决方案

​ 内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值