内存分区
int a = 0; //(GVAR)全局初始化区
int* p1; //(bss)全局未初始化区
int main() //(text)代码区
{
int b; //(static)栈区变量
char s[] = "abc"; //(static)栈区变量
int* p2 = NULL; //(static)栈区变量
char *p3 = "123456"; //p3在栈区,123456\0在常量区
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)代码区
}
栈空间和堆空间的内存增长方向不一样。栈空间是从高地址向低地址增长的过程,堆空间是从低地址向高地址增长的过程。内存正常都是从低地址向高地址增长。
栈空间中的变量定义好后,这个变量的内部存储、资源分配的操作完全交由编译器和系统来完成。堆区的变量由程序员动态的创建。当然我们new出空间后要记得delete,不然会造成内存泄漏。
在堆区和常量区之间还有一个常量区,图中没有体现出来。
常量区分为全局初始化区和全局未初始化区。
cpp动态分配和回收原则
堆
堆是一种动态分配资源的方式,从现代编程语言角度看,动态分配内存是一件很自然的事情。动态分配内存可以由程序员来掌握,但是会带来一些不确定性:内存分配耗时需要多久?失败了怎么办?在实时性要求比较高的场合,比如一些嵌入式控制器和电信设备,这些情况就不容忽视,所以这些场合一般使用C++而不是java(java一般全是在堆中分配)。
一般而言,但我们在堆上分配内存时,很多语言会使用new这样的关键字,有些语言则是隐式分配。在C++中new对应的是delete,这样可以让程序员完全接管内存的分配和释放。
程序通常需要牵涉到三个内存管理器的操作,C++做了1、2两件事,java做了1、3两件事:
- 分配一个某大小的内存块;
- 释放一个之前分配的内存块;
- 垃圾收集操作,寻找不再使用的内存块并予以释放。这个回收策略需要实现性能、实时性、额外开销等放面的平衡,很难有统一和高效的做法。
堆区和栈区变量对比
stack | heap | |
---|---|---|
作用域 | 函数体内,语句块{}作用域 内 | 整个程序范围内,由new,malloc开始,delete、free结束 |
编译间大小确定 | 变量大小范围确定 | 变量大小范围不确定。需要运行期间确定 |
大小范围 | Windows系统默认栈大小是1M,linux默认大小是8M或10M,用ulinit -s查看。栈区很小,我们如果在栈区定义一个很大的变量是有风险的 | 所有系统的堆空间上限是接近内存(虚拟内存)的总大小(一部分被OS占用) |
内存分配方式 | 地址由高到低减少 | 地址由低到高增加 |
内容是否可变 | 可变 | 可变 |
全局静态存储区和常量存储区的变量对比
全局静态存储区 | 常量存储区 | |
---|---|---|
存储内容 | 全局变量,静态变量 | 常量 |
编译期间大小是否确定 | 确定 | 确定 |
内容是否可变 | 可变 | 不可变 |
内存泄漏
内存泄漏是指程序中已经动态分配的堆内存由于某种原因程序没有释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统程序崩溃等严重后果。
内存泄漏发生原因和排查:
- 内存泄漏主要发生在堆内存分配方式中,即“配置了内存后,所有指向该内存的指针都遗失了”。若缺乏垃圾回收机制,这样的内存就无法归还系统。
- 因为内存泄漏属于程序运行中的问题,无法通过编译来识别,所以只能在程序运行过程中来判别和诊断。