《重学C++》5. 彻底学会 C++ 指针,引用(二)内存资源

1.CPP程序的存储区域划分总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int a = 0;				//(GVAR)全局初始化区
int* p1; //(bss)全局未初始化区

int main() //(text)代码区
{
int b = 1; //(stack)栈区变量
char s[] = "abc"; //(stack)栈区变量
int* p2 = NULL; //(stack)栈区变量
const char* p3 = "123456";//123456\0在常量区,p3在(stack)栈区
static int c = 0; //(GVAR)全局(静态)初始化区
p1 = new int(10); //(heap)堆区变量
p2 = new int(20); //(heap)堆区变量
char* p4 = new char[7];//(heap)堆区变量
strcpy_s(p4, 7, "123456"); //这两个语句在(text)代码区

return 0; //这两个语句在(text)代码区
}

同样是指针变量,p1本身存在全局区、p2、p3、p4本身存储在栈区;
而同样被指针指, “123456”存储在常量区(因此p3不能更改里面的值)、p1和p2指向的数字存在堆区、p4指向的字符数组也存在堆区。


另外:栈区中地址从大到小增长,而堆中地址从小到大。

栈和堆相向增长

堆中分为全局初始化区(GVAR)和全局未初始化区(bss)。
由于未初始化的变量只需要用0标记,因此不需要为未初始化的全局变量专门分配存储空间。


2. CPP动态分配和回收原则

动态分配资源——堆(heap):

从现代的编程语言角度来看,使用堆,或者说使用动态内存分配,是一件很自然不过的事情。但并不是说这应该成为唯一的分配方式。
动态内存带来了不确定性:内存分配耗时需要多久?失败了怎么办?在实时性要求比较高的场合,如一些嵌入式控制器和电信设备。
一般而言,当我们在堆上分配内存时,很多语言会使用new这样的关键字,有些语言则是隐式分配。在C++中new的对应词是delete,因为C++是可以让程序员完全接管内存的分配释放的(Java不能接管释放)。

分配和回收动态内存的原则

程序通常牵扯到三个内存管理器的操作:

  1. 分配一个某个大小的内存块;
  2. 释放一个之前分配的内存块;
  3. 垃圾收集操作,寻找不再使用的内存块并予以释放;这个回收策略需要实现性能、实时性、额外开销等各方面的平衡,很难有统一和高效的做法;

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语言这样的垃圾回收机制,这样的内存片就无法归还系统。
因为内存泄漏属于程序运行中的问题,无法通过编译识别,所以只能在程序运行过程中来判断和诊断