C++内存管理:内存管理基础、new和malloc、智能指针引入
1.为什么要研究内存管理
1.1 程序就是数据加算法
写程序是为了解决某个问题,生活中的问题最终被计算机抽象为控制或运算。CPU中的主要构件就是运算器和控制器,本质上是一堆组合逻辑电路,表现为机器指令集。一个问题对应一个程序,一个程序对应为多个函数,一个函数对应为多个机器指令。存储机器指令需要内存,机器指令的执行过程需要内存参与,这是内存的2大作用。算法对应机器指令(ROM内存),数据对应RAM内存,CPU对应工作机器。越是偏底层的语言,越对内存管理具体化,效率也越高,同时对编程者要求也越高。
1.2 计算机中如何管理内存
C++
项目大多数对应在操作系统中运行,很少有裸机的。操作系统提供最基本的内存管理体系,操作系统直接管理物理内存,并向应用层提供一套内存管理的接口。
C++
语言对操作系统的内存接口进行封装,提供给程序员一套内存使用方法。编程人员写的代码在编译工具链、运行时环境、操作系统等体系的帮助和转换下最终在计算机物理层上运行。
1.3 总结
内存管理的原理虽然庞大而复杂,但是经过层层封装,编程的人只需要掌握好C++
语言的内存管理语言特性即可。
C++
程序容易出bug,主要就是因为内存管理部分的复杂性;Java
、Python
等语言提供了更多的封装,所以降低了程序员操作难度和犯错可能性。
2.C++可用内存区域
2.1 C语言可用内存区域
- 栈区(
stack
):对应局部变量。 - 全局数据区/静态数据区:对应全局变量,静态局部变量。
- 代码段:放可执行程序的,实质是
Rom
。 - 堆区(
heap
):由malloc
和free
来管理的一块随借随还的内存。 const
数据区:在物理内存层面是不存在的,是C编译器伪造出来的,本质就是普通内存。
2.2 C++新增内存区域
C++
是兼容C
的,因此上面C
语言可以使用的内存区域C++
也可以使用。除此之外C++
有以下新增的区域:
- 自由存储区,由关键字
new
申请得到的动态内存区域。
C++
自由存储区是否等价于C
中的堆?堆是操作系统维护的一块提供了动态分配功能的内存,当运行程序调用malloc()
时就会从中分配,稍后调用free()
可把内存交还,是一个物理概念,而自由存储是C++
中通过new
与delete
动态分配和释放的对象的存储区,是一个逻辑概念。基本上所有的C++
编译器默认使用堆来实现自由存储区,new
和delete
也许会按照malloc()
和free()
的方式来被实现,这时由new
运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,这时自由存储区就区别于堆了。
2.3 总结
代码段只会读不会写,一般不会出任何问题。全局数据区和栈区都是系统自动管理的,只要可用内存足够一般也不会出问题。const
数据区实际上是由编译器来保证只读的,本质就是普通的内存区域,只要代码骗过编译器(比如通过指针来修改),该数据区的数据同样可以修改。
灵活性和风险都集中在堆区(heap
)区域,常见问题如内存泄漏、内存碎片等。
3.new和malloc的区别
3.1 简单区别
- 1.
malloc()
是C
库函数,不是语言内置而是库函数扩展出来的功能,new
是C++
运算符关键字。 - 2.
malloc()
申请空间大小靠传参确定,而new
不需要传参,对象本身大小由编译器自动计算给出。 - 3.
malloc()
返回值为void *
因此需要进行强制类型转换,而new
返回值类型为确定的对象指针类型。 - 4.
malloc()
对应free()
释放,new
对应delete
和delete[]
释放。
3.2 深度区别
- 1.
malloc()
只能申请内存不能带初始化,一般在后接上一个memset()
来初始化,而new
可以带初始化。 - 2.
new
会执行类的构造函数而malloc()
不会。 - 3.
malloc()
失败返回NULL
,而new
失败引发bad_alloc
异常。 - 4.申请和释放数组类型时不同。
3.3 总结
Linux
平台中new
内部是通过malloc()
实现的,new
比malloc()
多一个调用构造函数,malloc()
只是返回一块荒地,而new
会返回规划好的空间。在C++
中可以没有malloc()
,留下malloc()
只是为了兼容C
。
4.智能指针引入
4.1 指针的优势和劣势
指针的本质是一个变量,变量的值是其他对象的地址,因此可以解引用。指针本质上对应CPU指令中的间接寻址,所以指针是天然存在的,是CPU设计决定了的。
指针的优势就是灵活、代码效率高。指针的劣势也是太灵活,尤其结合动态内存和构造、析构后,在复杂业务中容易出错。
4.2 如何解决
首先要知道程序底层不用指针是不可能的,因此这个问题是绕不开的。
- 解决方案1:由程序员来自主把控,
C/C++
典型编程就是这样。 - 解决方案2:由程序员和专门设计的自动管理机制共同把控,典型代表是智能指针。
- 解决方案3:由自动管理机制全权把控,程序员不用管,典型代表是
Java
的垃圾回收机制。
4.3 智能指针如何实现
将普通的简单纯指针封装为栈式复合指针对象,即智能指针对象(封装成struct
或class
,内部除了实际指向数据的指针外还有一些其他完成必要功能的成员)。
智能指针本身定义为局部变量,分配在栈内存上,因此本身是自动回收的。智能指针内部设计为当智能指针本身要被弹栈释放时,执行事先挂接好的清理函数。智能指针的正常使用通过一些提供的方法和运算符重载来使用
4.4 智能指针总结
智能指针是普通指针的升级版,封装版,本身具备指针的功能且多出一些自动释放资源的机制。智能指针进行动态内存管理要比普通指针多出很多内存和性能上的开销。
智能指针的实现不是唯一的,C++
有很多种智能指针,它们各有优劣和适用场景,如:std::auto_ptr
、boost::scoped_ptr
、boost::shared_ptr
、boost::scoped_array
、boost::shared_array
、boost::weak_ptr
、boost::intrusive_ptr
。
智能指针也要按照规则去正确使用,否则也会出问题,具体用法后续进一步理解。
5.Java的垃圾回收机制
5.1 Java语言整体框架
Java
语言从低到上的框架依次为:
- 1.CPU
- 2.操作系统内核(
Linux
,Windows
等) - 3.操作系统应用层框架
- 4.
JVM
(Java
虚拟机,通过操作系统应用层接口写出来的Java
程序运行环境) - 5.
Java
字节码(用Java
源代码编译出来的Java
可执行程序) - 6.
Java
源代码
Java
是解释型语言,而非编译型语言,Java
虚拟机是Java
语言的运行时环境,也是Java
语言跨平台的关键。
5.2 Java的垃圾回收机制
垃圾是指使用完毕后待回收的内存资源,本质是生命周期结束了的变量对象等。垃圾由GC
线程(Java
虚拟机中的守护线程)负责回收。GC
什么时候回收垃圾由机制和算法来决定,程序员不用管。通过引用计数法和可达性分析法来确定哪些对象成为了垃圾。
5.3 垃圾回收机制评价
垃圾回收机制不止Java
才有,其他语言如C#
也是类似设计理念,典型特征就是语言没有指针的概念。垃圾回收机制让程序员免于考虑对象的生命周期和资源的申请与释放,编程难度大减。
垃圾回收机制的稳定性和效率取决于运行时环境(JVM
等)设计和实现的好坏。垃圾回收机制用效率和内存资源成本,换来了更简单不易错的语言特性。
5.4 最后的总结
机制本身有优点就有缺点,没有绝对好坏,否则绝对好的那个早就把不好的淘汰了,适合的场景使用适合的机制才是上策。C++
掌握好以后再学习Java
、Python
等语言就简单多了