欢迎访问mehome的博客

Tomorrow is another day.
Fork me on GitHub

C++内存分区

一、内存分几个区原因

系统运行时不能所有数据都读取到CPU寄存器,所以需要有缓存,缓存不够用了所以有多级缓存。缓存的存储器毕竟还是相对昂贵的,所以还有内存(也叫外存)。但是数据在内存中的是怎样存放,这又是一个问题。

  1. 首先从字节层面看:信息在计算机肯定是0/1的bit比特位形式存储,习惯是8bit作为一个字节作为一个有意义的存储单位,但是有些数据的最小存储单位是多个字节,例如:int类型。多字节的数据在内存或者寄存器存储方式可以分为:大端、小端。
  2. 整体内存层面看:内存中有执行的二进制代码程序、有代码运行时需要不断改变的变量、有代码运行调用但是不需要改变的常量、还有需要的数据等等。内存中的数据可以看做多小电容,不停读写修改会让这些电容充放电,充放电频繁会产生磁场对附近的其他数据影响。为了抵消这些影响,系统把内存分为多个区,这样可以有效提升数据的安全性。企业级服务器使用的一般都是带ECC(Error Correcting Code)错误检查纠正技术的内存


二、主流说法

程序在内存中主要分几个区域,每个区域存储那些数据(此处把程序、变量等都看做数据),主要有以下几种说法:

  1. 第一种说法是:5个区域(堆区、栈区、全局/静态存储区、常量存储区、自由存储区)。
  2. 第二种说法是:5个区域(堆区、栈区、全局/静态区、(字符)常量存储区、程序代码区)。
  3. 第三种说法是:5个区域(bss是英文Block Started by Symbol的简称、text段是程序代码段、data包含静态初始化的数据、stack保存函数的局部变量和参数、heap保存函数内部动态分配内存)
  4. 第四种说法是:4个区域(堆区、栈区、全局/静态存储区、常量区)。

2.1相同部分对比


堆区

栈区

全局/静态存储区

  1. 堆是“先进先出”(First In first OutFIFO)数据结构。
  2. 一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统(OS)回收。
  3. 注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
  4. 它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。
  5. 有的说法是:程序员分配通过malloc申请的动态内存在堆区创建,free来结束并释放;new创建的对象在自由存储区创建。new申请的内存需要程序员delete,malloc申请的内存需要free释放,从而防止内存泄漏。对于堆来说,释放工作由程序员控制,容易产生memory leak


  1. 栈是一种“后进先出”(Last In First OutLIFO)的数据结构。
  2. 由编译器在需要的时候分配,在不需要的时候自动清除。
  3. 存放函数的参数值,局部变量的值等
  4. 栈另外一个重要的特征是,它的地 址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
  1. 全局变量和静态变量被分配此区域(在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区)
  2. 段的起始位置也是由连接定位文件所确定。
  3. 大小在编译连接时自动分配,它和程序大小没有关系,但和程序使用到的全局变量,常量数量相关。

注意:堆区数据有先进先出特性,栈区数据有后进先出特性,但是并不是说只能依次取数据。具体怎么取堆/栈区中间数据需要看堆/栈是怎么实现的。如果栈是用常见的最大/最小树结构实现,那么就是取中间数据后再对其进行排序即可。


2.2自由存储区、堆区

主要讨论点是:自由存储区(free store),堆区有什么区别

  • 很多博客划分自由存储区与堆的分界线就是new/delete与malloc/free。
  • 然而,尽管C++标准没有要求,但很多编译器的new/delete都是以malloc/free为基础来实现的。那么借以malloc实现的new,所申请的内存是在堆上还是在自由存储区上?
  • 从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。
    自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。
  • 基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。我们所需要记住的就是:
       堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。


2.3堆区和栈区区别


堆区和栈区主要区别集中在以下几点:

  1. 管理方式不同。
  2. 空间大小不同。
  3. 能否产生碎片不同。
  4. 生长方向不同。
  5. 分配方式不同。
  6. 分配效率不同。


具体区别参考下表:


堆区

栈区

  1. 堆区的使用由程序员通过new/malloc申请,需要由程序员显式的delete/free释放,如果没有显式是放程序结束系统会自动回收,但是程序运行时由内存泄漏的风险。
  2. 一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。
  3. 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。
  4. 对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向。
  5. 堆都是动态分配的,没有静态分配的堆。
  6. 堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
  1. 由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数。
  2. 对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是xM,x往往是固定的。比较小。
  3. 对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出
  4. 对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
  5. 栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  6. 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。


  • 堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片。
  • 由于没有专门的系统支持,效率很低。
  • 由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。
  • 所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。
  • 所以,使用的时候尽量用栈,而不是用堆。


  • 虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。 
  • 无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的 结果,就算是在程序运行过程中,没有发生上面的问题,还是要小心,说不定什么时候就崩掉。

2.4数据结构中的堆栈

以上的内存分区中的堆栈并不对应数据结构中的堆/栈。

2.4.1数据结构中的堆

堆(Heap)计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。

堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。

2.4.2数据结构中的栈

栈(stack)限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈。

原来的意思,如此才能把握本质。栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。

的特点后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针

三、栈使用

对内存分区有一些了解,知道为什么内存中数据要分开存,然后了解大概可以分几个区即可。具体分几个,new的对象存储在哪里,malloc申请的内存在哪里这些细节需要看具体的编译器和操作系统。如果不是做编译器的,可以对这些有了解即可。

3.1栈空间分配和栈溢出

栈空间分配:从高地址向低地址依次分配栈变量/对象空间(注意:栈变量/对象的数据的存储顺序遵从大端、小端规则,这个大端小端和栈空间分配顺序不一定一致);

栈溢出:

  1. 根据栈空间分配规则,可以设想如果有多个变量/对象时,变量/对象定义(不是声明)的顺序决定这个变量/对象在栈空间的位置。
  2. 先定义的在高地址空间,后分配变量在低地址空间。
  3. 后分配的栈变量/对象发生溢出时,是向高地址溢出,会覆盖定义顺序在前面的栈变量内容(Windows visual studio)。

测试代码:


  1 #include <iostream>
  2 #include <string>
  3 
  4 void stackOverflow(char *data) {
  5 
  6     char fillData[18];
  7     memset(fillData, 'A', sizeof(fillData));
  8     int s = sizeof fillData;
  9     //内存拷贝到data,实际拷贝数据量比data内存空间大
 10     //溢出后向高地址空间覆盖
 11     memcpy(data, fillData, sizeof(fillData));
 12 }
 13 
 14 int main()
 15 {
 16     char mainData[6] = "hello";
 17     char overflowData[6];
 18     memset(overflowData, 'B', sizeof(overflowData));
 19     std::cout << "before:\t"<< mainData << std::endl;
 20     //overflowData溢出后向高地址覆盖
 21     //会覆盖mainData空间内容
 22     stackOverflow(overflowData);
 23     std::cout << "after:\t" << mainData << std::endl;
 24     getchar();
 25 }
  • 执行结果:

image


四、相关参考

非常感谢以下的博客文章:

posted @ 2020-09-04 21:02  mehome  阅读(417)  评论(0编辑  收藏  举报