《重学C++》5. 彻底学会 C++ 指针,引用(二)内存资源
1.CPP程序的存储区域划分总结
1 | int a = 0; //(GVAR)全局初始化区 |
同样是指针变量,p1本身存在全局区、p2、p3、p4本身存储在栈区;
而同样被指针指, “123456”存储在常量区(因此p3不能更改里面的值)、p1和p2指向的数字存在堆区、p4指向的字符数组也存在堆区。
另外:栈区中地址从大到小增长,而堆中地址从小到大。
堆中分为全局初始化区(GVAR)和全局未初始化区(bss)。
由于未初始化的变量只需要用0标记,因此不需要为未初始化的全局变量专门分配存储空间。
2. CPP动态分配和回收原则
动态分配资源——堆(heap):
从现代的编程语言角度来看,使用堆,或者说使用动态内存分配,是一件很自然不过的事情。但并不是说这应该成为唯一的分配方式。
动态内存带来了不确定性:内存分配耗时需要多久?失败了怎么办?在实时性要求比较高的场合,如一些嵌入式控制器和电信设备。
一般而言,当我们在堆上分配内存时,很多语言会使用new这样的关键字,有些语言则是隐式分配。在C++中new的对应词是delete,因为C++是可以让程序员完全接管内存的分配释放的(Java不能接管释放)。
分配和回收动态内存的原则
程序通常牵扯到三个内存管理器的操作:
- 分配一个某个大小的内存块;
- 释放一个之前分配的内存块;
- 垃圾收集操作,寻找不再使用的内存块并予以释放;这个回收策略需要实现性能、实时性、额外开销等各方面的平衡,很难有统一和高效的做法;
C++做了1,2两件事;而Java则做了1,3两件事;
3. RAII资源管理
- RAII(Resource Acquisition Is Initialization)是主流语言中C++所独有的资源管理方式;
- RAII依托栈和析构函数,来对所有的资源——包括堆内存在内——进行管理。对RAII的使用,使得C++不需要类似于Java那样的垃圾收集方法,也能有效地对内存进行管理。RAII的存在,也是垃圾收集虽然理论上可以在C++使用,但从来没有真正流行过的主要原因;
- RAII有些比较成熟的智能指针代表:如std::auto_ptr和boost::shared_ptr
4. 几种变量对比
栈 VS 堆
| 栈(stack)区 | 堆(heap)区 |
---|---|---|
作用域 | 函数体内,语句块{}作用域; | 整个程序范围内,由new,malloc开始,delete,free结束; |
编译间大小确定 | 变量大小范围确定 | 变量大小范围不确定,需要运行期确定; |
大小范围 | Windows系统默认栈大小是1M,linux常见默认的栈大小是8M或10M(通过ulimit -s查看;不同linux发行版的命令不保证相同) | 所有系统的堆空间上限是接近内存(虚拟内存)的总大小的(一部分被OS占用); |
内存分配方式 | 地址由高到低减少 | 地址由低到高增加 |
内存是否可变 | 可变 | 可变 |
不应该通过栈做负荷量大的工作,而是应该用堆,因为栈容量小。更严重的是调试时未必会出错,因为当时工作量可能并不大。
全局静态存储区 VS 变量存储区
| 全局静态存储区 | 常量存储区 |
---|---|---|
存储内容 | 全局变量,静态变量 | 常量 |
编译期间大小是否确定 | 确定 | 确定 |
内容是否可变 | 可变 | 不可变 |
5. 内存泄漏
内存泄漏:指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
我们平时程序运行时间很短,即使泄露也会很快结束程序并释放内存,那为什么内存泄露会造成严重的资源浪费?
因为服务器程序不同于个人电脑中的程序,它需要很长时间运行而且不能轻易关闭/重启。内存泄漏每次可能只浪费一点点,但由于服务器程序的特点,会导致内存长时间泄露+无法及时归还,导致最终会造成严重后果。
内存泄漏发生原因和排查方式:
内存泄漏主要发生在堆内存分配方式中,即”配置了内存后,所有指向该内存的指针都遗失了”。若缺乏java语言这样的垃圾回收机制,这样的内存片就无法归还系统。
因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判断和诊断。