C++ 程序的内存分配
- C++ 程序的内存分配
- 栈
- 堆
- 数据区
- 程序代码区
- 参考
C++ 程序的内存分配
一个 C++ 编译的程序占用内存分为以下几个部分(从高地址到低地址):
- 内核空间:由操作系统创建并控制,用户代码不能读写。
- 栈:由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
- 堆:一般由程序员自动分配,如果程序员没有释放,程序结束时可能由操作系统回收。其分配类似于链表。
- 数据区:存放全局变量、静态数据、常量。程序结束后由系统释放。可以细分为全局/静态存储区和文字常量区。而全局区分为已初始化全局区(data)和未初始化全局区(bss)。
- 程序代码区:存放程序的二进制代码。
实例:
栈
由编译器在编译时自动分配和释放。
进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率高。
栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。
栈能申请的最大分配大小是多少?为什么?
通过命令 ulimit -s 查看linux的默认栈空间大小,默认情况下为8192 KB,即8MB。也可以临时修改栈空间的默认大小,ulimit -s 102400,即修改为100MB。
限制栈的大小主要是因为栈的地址空间必须连续,如果任其任意成长,会内存溢出(Stack Overflow)。一般遇到这个问题有两种常见情况:
- 定义了较大的局部变量,导致超出了栈的最大内存。
- 递归调用的层次较深,由于在函数递归调用中,局部变量都要到递归结束后才能释放,所以很容易导致内存溢出。解决方法是在调用的过程中使用动态内存分配。
当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。
堆
堆用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。
堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。
在C++中,malloc/new申请的内存都是从堆内存上分配的,用完后由free/delete区释放的。如果没有释放堆内存,则会造成内存泄漏,程序结束时由操作系统统一回收。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
数据区
全局变量和静态变量的内存就是在该区上分配的,全局变量和静态变量的生命周期也是一样的,都是在程序启动时分配内存,在程序退出时释放内存。
全局变量一般会使用extern关键字来声明:
extern int m_nClientId;
而静态变量则是使用static关键字来声明:
static int nCount;
数据区分为2个区域:
- 全局/静态存储区:分为已初始化全局区(data)和未初始化全局区(bss)。
- 文字常量区(text):存放常量字符串,而且不允许修改。程序结束后由系统释放。
图例:
text段和data段在编译时已经分配了空间,而bss段并不占用可执行文件的大小,它是由链接器来获取内存的。bss段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为0。
程序代码区
程序代码区存放函数体(类成员函数和全局区)的二进制代码。
这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量。
参考
- https://www.jb51.net/article/226330.htm
- https://blog.csdn.net/tjcwt2011/article/details/132189598
- https://blog.csdn.net/daaikuaichuan/article/details/89006402
- https://blog.csdn.net/weixin_56054625/article/details/136047382
- https://blog.csdn.net/qq_72982923/article/details/132197354