C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】....

前言

本章内容结合了三本C++书籍归纳总结,讲述了动态内存分配,智能指针等用法以及使用中的注意事项。

文章目录

    • 前言
    • 一、回顾内存中是如何分区的
      • 1、程序运行前,内存中的变化
      • 2、程序运行后,内存中的变化
      • 3、malloc内存块
    • 二、 动态内存分配
      • 1、内存分配出现的问题
        • 1.1 内存泄漏
          • 内存泄漏为什么会降低性能???
          • 几种常见造成原因:
        • 1.2 内存溢出
          • 常用造成溢出原因:
      • 2、new与delete的搭配
        • 2.1 new
          • 2.1.1 new的刨析
        • 2.2 delete
        • 2.3 验证是否能直接调用构造函数
      • 3、placement new
        • 3.1 使用placement new 的好处是什么??
        • 3.2 重载placement new
        • 3.3 如何删除placement new分配的内存

C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【下】…

一、回顾内存中是如何分区的

程序执行过程分为四个区域:

  • 代码区:存放函数体二进制代码,由系统管理;

  • 全局区:存放全局变量和静态变量以及常量;

  • 栈区:由编译器自动分配释放,存放函数的参数值以及局部变量

  • 堆区:由程序员分配和释放,若不释放,则系统结束后会自动回收;

1、程序运行前,内存中的变化

当程序编译成功后,即生成了exe可执行文件,在未开始执行该程序前:

代码区

  • 存放cpu执行的机器命令

  • 代码区是共享的,共享的目的是对于频繁被执行的程序,而内存中只需要有一份代码即可

  • 代码区是只读的,原因是防止程序意外地修改了它的指令

全局区

  • 存放静态变量和全局变量;

  • 全局区包含了常量区,存放常量(不包括局部常量);

  • 该区域的数据在程序结束后由系统释放;

2、程序运行后,内存中的变化

由编译器自动分配释放,存放函数的参数值局部变量

  • 不要返回局部变量的地址,栈区开辟的数据由编译器自动释放;

3、malloc内存块

频繁使用malloc会增加额外的不必要的开销;上下会带上总数为8字节的cookie;

  • 由于是16的倍数,则字节数不够会自动填充间隔,进一步造成空间上的浪费;

  • debug的内存块在debug模式下,才会出现;

C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第1张图片

申请数组
C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第2张图片

二、 动态内存分配

由上述回顾可知,程序中有一个内存池供程序员自行分配管理。在C语言中使用可mallocfree对该内存的申请和释放,而在C++中简化了该语句使用newdelete对内存的申请和释放;

> 堆分配
> 栈分配
> 全局和静态内存分配
> 其中堆分配可采用动态内存分配

1、内存分配出现的问题

1.1 内存泄漏

因程序对内存管理失当,失去对一段已分配内存空间的控制,造成程序继续占用已经不再使用的内存空间,或是存储器所存储之对象无法透过执行代码而访问,令内存资源空耗;

  • 导致计算机性能下降;
  • 应用程序崩溃等问题;
内存泄漏为什么会降低性能???

内存分配是动态的,每个进程根据要求获得相应的内存。访问活跃的页面文件被转移到主内存以提高访问速度;反之,访问不活跃的页面文件被转移到次级存储设备。当一个简单的进程消耗大量的内存时,它通常占用越来越多的主内存,使其他程序转到次级存储设备,使系统的运行效率大大降低。甚至在有内存泄漏的程序终止后,其他程序需要相当长的时间才能切换到主内存,恢复原来的运行效率;

几种常见造成原因:
> malloc一块内存使用但经初始化;
> free掉的内存继续使用;
> malloc的内存,再函数中出现异常中断,故没有执行free进行释放;

1.2 内存溢出

可能由内存泄漏的堆积最终会导致内存溢出,内存不足,当程序需要使用的内存超出系统所提供的范围;

  • 故当栈空间满时,继续向栈中压入数据,此时会造成栈上溢出;
常用造成溢出原因:
> 内存中加载的数据量过于庞大;
> 查询数据库没有关闭游标

2、new与delete的搭配

2.1 new

在自由空间分配的内存是无名的,故new无法为其分配的对象命名,单返回一个指向该对象的指针

2.1.1 new的刨析

当使用new创建一个对象时:

  • 第一步先分配内存调用operator newoperator new内部在调用malloc

  • 第二步进行类型转换

  • 最后在调用对象的构造函数

  • new的行为总是这样的,不能改变,但我们可以修改如何为对象分配内存;

C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第3张图片

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;

2.2 delete

当delete释放一个对象时:

  • 针对此内存会有一个/多个析构函数被调用;
  • 内存释放通过operator delete
  • 编译器不能辨别该指针是否被释放
  • 数组中释放按照逆序释放;

C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第4张图片

注意delete的释放单一对象和数组对象的区别

由于单一对象的内存布局一般不同于数组的内存布局;

  • 数组的内存通常会记录数组的大小,便于delete释放空间时,析构调用的次数;
  • 释放数组对象时,需添加[]告诉编译器该对象为数组对象;

若数组适用delete将会导致释放一次,数组中的其他块会造成内存泄漏
C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第5张图片

delete 对象;
delete[] 数组对象;	

2.3 验证是否能直接调用构造函数

根据以下运行结果可知,编译器给出错误不能调用构造函数;
>>> 但阔以调用【析构函数】;
#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;
}

在这里插入图片描述

3、placement new

能够将new的内存分配以及在内存中构建对象的事情分离出来;

  • 一般用法是将分配好的内存传入,在改内存上构造对象;

3.1 使用placement new 的好处是什么??

  • 由于使用预分配好的内存,一般不会出现分配失败的危险;
  • 在预分配的缓冲区上构建对象的需要时间更短
  • 能够对容纳多个对象的内存块中执行单个分配

案例

/** 一般使用:普通的堆分配 */
string *q = new string("hi");         

/** 使用placement new */ 
char *buf  = new char[sizeof(string)]; // 预先分配内存
string *p = new (buf) string("hi");    // placement new

3.2 重载placement new

一般重载class内部的menber newplacement 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;
}

C++ |【总结归纳三本书籍系列】一文透彻资源管理,动态内存分配【上】...._第6张图片

3.3 如何删除placement new分配的内存

当我们使用placement new时,需要手动进行调用析构函数;

为什么呢???使用delete不好嘛?

因为当我们使用时,是用new来分配内存,而placement new只是用来创建对象,故改对象使用过后直接调用析构即可,释放内存应该交给与new相对于的delete去操作;

  • 即将placement new创建的对象析构并不是将该内存还给系统,即可继续供下次创建对象使用;

若传入的只是变量的地址,没有使用new分配内存,则无法delete;

A aa;
A* a = new(&aa)A(2);

你可能感兴趣的:(C++,内存管理,c++,开发语言,后端,内存,智能指针)