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、事后查错型。如泄漏检测工具。