本章内容结合了三本C++书籍归纳总结,讲述了动态内存分配,智能指针等用法以及使用中的注意事项。
C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【下】…
程序执行过程分为四个区域:
代码区:存放函数体
二进制代码
,由系统管理;全局区:存放
全局
变量和静态
变量以及常量;栈区:由编译器
自动分配
释放,存放函数的参数值
以及局部变量
;堆区:由
程序员
分配和释放,若不释放,则系统结束后会自动回收;
当程序编译成功后,即生成了exe可执行文件,在未开始执行该程序前:
代码区:
存放cpu执行的
机器命令
;代码区是
共享
的,共享的目的是对于频繁被执行
的程序,而内存中只需要有一份代码即可
;代码区是
只读
的,原因是防止程序
意外地修改
了它的指令
;全局区:
存放静态变量和全局变量;
全局区包含了常量区,存放常量(不包括局部常量);
该区域的数据在程序结束后由系统释放;
由编译器
自动分配释放
,存放函数的参数值
,局部变量
;
- 不要返回局部变量的地址,
栈区
开辟的数据由编译器自动释放;
频繁使用
malloc
会增加额外的不必要的开销;上下会带上总数为8字节
的cookie;
由于是
16
的倍数,则字节数不够会自动填充间隔
,进一步造成空间上的浪费;
debug
的内存块在debug模式下,才会出现;
由上述回顾可知,程序中有一个内存池供程序员自行分配管理。在C语言中使用可
malloc
和free
对该内存的申请和释放,而在C++中简化了该语句使用new
和delete
对内存的申请和释放;
> 堆分配
> 栈分配
> 全局和静态内存分配
> 其中堆分配可采用动态内存分配
因程序对内存管理失当,失去对一段
已分配内存
空间的控制
,造成程序继续占用
已经不再使用的内存空间
,或是存储器所存储之对象无法透过执行代码而访问
,令内存资源空耗;
- 导致计算机性能下降;
- 应用程序崩溃等问题;
内存分配是动态的,每个进程根据要求获得
相应的内存
。访问活跃的页面文件被转移到主内存
以提高访问速度;反之,访问不活跃的页面文件被转移到次级存储设备
。当一个简单的进程消耗大量
的内存时,它通常占用越来越多的主内存
,使其他程序转到次级存储设备,使系统的运行效率大大降低
。甚至在有内存泄漏的程序终止后,其他程序需要相当长的时间
才能切换到主内存,恢复原来的运行效率;
> malloc一块内存使用但经初始化;
> free掉的内存继续使用;
> malloc的内存,再函数中出现异常中断,故没有执行free进行释放;
可能由内存泄漏的
堆积
最终会导致内存溢出,内存不足,当程序需要使用的内存超出系统所提供的范围;
- 故当栈空间满时,继续向栈中压入数据,此时会造成栈上溢出;
> 内存中加载的数据量过于庞大;
> 查询数据库没有关闭游标
在自由空间分配的内存是无名的,故new无法为其分配的
对象命名
,单返回一个指向该对象的指针
;
当使用new创建一个对象时:
第一步先分配内存调用
operator new
,operator new
内部在调用malloc
;第二步进行
类型转换
;最后在调用对象的
构造函数
;new的行为总是这样的,不能改变,但我们可以修改如何为对象分配内存;
int *p = new int(); // 默认初始化为0
int *p1 = new int; // 默认初始化,未定义
当申请一个const对象
const必须在进行
动态分配
的时候就进行初始化
;
const int *p = new const int(1);
new内的bad_alloc
当new的内存分配的空间没有达到要求时,会抛出异常,而
bad_alloc
会阻止抛出异常;
int *p = new (nothrow) int;
数组
// 一维数组
int *arr = new int[20];
delete[] arr;
// 二维数组
int **ar = new int*[20];
for (int i = 0; i < 20; ++i)
delete[] ar[i];
delete[] ar;
// 方式二
int(*p)[20] = new int[3][20];
for (int i = 0; i < 20; ++i)
delete[] p[i];
delete[] p;
当delete释放一个对象时:
- 针对此内存会有一个/多个析构函数被调用;
- 内存释放通过
operator delete
;- 编译器
不能辨别
该指针是否被释放
;- 数组中释放按照
逆序
释放;
注意delete的释放单一对象和数组对象的区别
由于
单一对象
的内存布局一般不同于数组
的内存布局;
- 数组的内存通常会
记录数组的大小
,便于delete释放空间时,析构调用的次数;- 释放数组对象时,需添加
[]
告诉编译器该对象为数组对象;
若数组适用delete将会导致释放一次,数组中的其他块会造成内存泄漏
delete 对象;
delete[] 数组对象;
根据以下运行结果可知,编译器给出错误不能调用构造函数;
>>> 但阔以调用【析构函数】;
#include
using namespace std;
class A{
public:
int m_a;
A() {
cout << "construction A" << endl;
}
A(int a) : m_a(a) {
cout << "construction A: init m_a" << endl;
}
~A() {
cout << "destruct A" << endl;
}
};
int main() {
A *a = new A(1);
a->A::~A();
a->A::A(2);
delete a;
return 0;
}
能够将new的内存分配以及在内存中构建对象的事情分离出来;
- 一般用法是将分配好的内存传入,在改内存上构造对象;
- 由于使用预分配好的内存,一般
不会
出现分配失败
的危险;- 在预分配的缓冲区上构建对象的需要
时间更短
;- 能够对容纳多个对象的内存块中执行
单个分配
;
案例
/** 一般使用:普通的堆分配 */
string *q = new string("hi");
/** 使用placement new */
char *buf = new char[sizeof(string)]; // 预先分配内存
string *p = new (buf) string("hi"); // placement new
一般重载class内部的
menber new
,placement new
重载的第一个参数必须是size_t
类型;
#include
#include
using namespace std;
class A{
public:
int m_a;
A() {
cout << "construction A: " << this << endl;
}
A(int a) : m_a(a) {
cout << "construction A: init m_a: " << this << endl;
}
~A() {
cout << "destruct A" << endl;
}
void* operator new(size_t size) {
return malloc(size);
}
void* operator new(size_t size, void* args) {
return (A*)args;
}
void operator delete(void* p,size_t size) {
cout << "operator delete" << endl;
return free(p);
}
};
使用new
int main() {
A* aa = new A;
A* a = new(aa)A(2);
a->~A();
return 0;
}
创建数组存储对象
int main() {
A *a = new A[10];
A *ab = new(&a[0])A(1);
return 0;
}
当我们使用placement new时,需要
手动
进行调用析构
函数;
为什么呢???使用delete不好嘛?
因为当我们使用时,是用
new来分配内存
,而placement new
只是用来创建对象
,故改对象使用过后直接调用析构即可,释放内存应该交给与new相对于的delete
去操作;
- 即将
placement new
创建的对象析构并不是将该内存还给系统,即可继续供下次创建对象使用;
若传入的只是
变量的地址
,没有使用new分配内存,则无法delete;
A aa;
A* a = new(&aa)A(2);