C++内存管理学习堆和栈

来源:http://c.chinaitlab.com/basic/936306_2.html 

一 C++内存管理

1.内存分配方式 

  在讲解内存分配之前,首先,要了解程序在内存中都有什么区域,然后再详细分析各种分配方式。

 

1.1 C语言和C++内存分配区

  下面的三张图,图1图2是一种比较详细的C语言的内存区域分法。图3是典型的C++内存分布图,简单易懂;以下内存分配图,区别就是图1和2则分为初始化和未初始化静态变量区,图3中是全局变量区。

  C语言(图1和图2):(由低地址到高地址)

  a)正文段:用来存放程序执行代码。通常,正文段是可共享的。另外,正文段常常是只读的,一次防止程序由于意外修改其自身。

  b)初始化数据段:用来存放程序中已初始化的全局变量。数据段属于静态内存分配。

  c)非初始化数据段:通常称为BSS段, 用来存放程序中未初始化的全局变量。BSS是英文Block Started by Symbol(由符号开始的块)的简称。BSS段属于静态内存分配。 在程序开始执行之前,内核将此段中的数据初始化为0或者空指针

  d):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上 (堆被扩张)/释放的内存从堆中被剔除(堆被缩减)。

  e):栈又称堆栈, 存放程序的局部变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,栈用来传递参数和返回值。(为运行函数而分配的局部变量、函数参数、返回地址等存放在栈区)。由于栈 的先进先出特点,所以栈特别方便用来保存/恢复调用现场。

\
\

  图1 典型C语言内存分布区域 (UNIX高级环境编程) 图2 典型C语言内存分布区域

  C++(图3):

  根据《C++内存管理技术内幕》一书,在C++中,内存分成5个区,他们分别是堆,栈,自由存续区,全局/静态存续区,常量存续区

  a) :内存由编译器在需要时自动分配和释放。通常用来存储局部变量和函数参数。(为运行函数而分配的局部变量、函数参数、返回地址等存放在栈区)。栈运算分配内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  b) :内存使用new进行分配,使用delete或delete[]释放。如果未能对内存进行正确的释放,会造成内存泄漏。但在程序结束时,会由操作系统自动回收。

  c) 自由存储:使用malloc进行分配,使用free进行回收。和堆类似。

  d) 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,C语言中区分初始化和未初始化的,C++中不再区分了。(全局变量、静态数据、常量存放在全局数据区

  e) 常量存储区:存储常量,不允许被修改。

  这里,在一些资料中是这样定义C++内存分配的,可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。

  a)静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量

  b)栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  c)堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或 delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

\

  图3 典型c++内存区域

  总结:C++与C语言的内存分配存在一些不同,但是整体上就一致的,不会影响程序分析。就C++而言,不管是5部分还是3大部分,只是分法不一致,将5部分中的c)d)e)合在一起则是3部分的a)。

 

1.2 区分堆、栈、静态存储区

  我们通过代码段来看看对这样的三部分内存需要怎样的操作和不同,以及应该注意怎样的地方。

(1)静态存储区与栈区

  1: char* p = “Hello World1”; 2: char a[] = “Hello World2”; 3: p[2] = ‘A’; 4: a[2] = ‘A’; 5: char* p1 = “Hello World1;”

  这个程序是有错误的,错误发生在p[2] = ‘A’这行代码处,为什么呢,是变量p和变量数组a都存在于栈区的(任何临时变量都是处于栈区的,包括在main()函数中定义的变量)。但是,数据 “Hello World1”和数据“Hello World2”是存储于不同的区域的。

  因为数据“Hello World2”存在于数组中,所以,此数据存储于栈区对它修改是没有任何问题的。因为指针变量p仅仅能够存储某个存储空间的地址,数据“Hello World1”为字符串常量,所以存储在静态存储区。虽然通过p[2]可以访问到静态存储区中的第三个数据单元,即字符‘l’所在的存储的单元。但是因为 数据“Hello World1”为字符串常量,不可以改变,所以在程序运行时,会报告内存错误。并且,如果此时对p和p1输出的时候会发现p和p1里面保存的地址是完全相 同的。换句话说,在数据区只保留一份相同的数据

 

(2)堆与栈区别

  我们先通过例子1来直观的说明下栈与堆内存的区别,然后在细致分析例子2中的情况。

  例子1:

  1: void fn(){ 2: int* p = new int[5]; 3: }

  看到new,首先应该想到,我们分配了一块堆内存,那么指针p呢? 它分配的是一块栈内存,所以这句话的意思就是:栈内存中存放了一个指向一块堆内存的指针p程序会先确定在堆中分配内存的大小,然后调用 operator new分配内存,然后返回这块内存的首地址,放入栈中

  注意:这里为了简单并没有释放内存,那么该怎么去释放呢? 是deletep么? NO,错了,应该是delete [ ] p,这是告诉编译器:删除的是一个数组

  例子2:

  

复制代码
 1 int a = 0; //全局初始化区
 2 char *p1; //全局未初始化区
 3 int main()
 4 {
 5    int b; //
 6    char s[] = "abc"; //
 7    char *p2; //
 8    char *p3 = "123456"; // 123456\0在常量区,p3在栈上。 
 9    static int c =0; //全局(静态)初始化区 
10    p1 = (char *)malloc(10); 
11    p2 = (char *)malloc(20); 12: //分配得来得10和20字节的区域就在堆区。 
12    strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
13  }
复制代码

 

  例子3:

  

复制代码
 1 char* f1() 
 2 { 
 3     char* p = NULL; 
 4     char a; 
 5     p = &a;
 6     return p; 
 7 } 
 8 
 9 
10 char* f2() 
11 { 
12     char* p = NULL;
13     p =(char*) new char[4]; 
14     return p; 
15 }
复制代码

 

  这两个函数都是将某个存储空间的地址返回,二者有何区别呢?f1()函数虽然返回的是一个存储空间,但是此空间为临时空间。也就是说,此空间只 有短暂的生命周期,它的生命周期在函数f1()调用结束时,也就失去了它的生命价值,即:此空间被释放掉。所以,当调用f1()函数时,如果程序中有下面 的语句:

  1: char* p ; 2: p = f1(); 3: *p = ‘a’;

  此时,编译并不会报告错误,但是在程序运行时,会发生异常错误。因为,你对不应该操作的内存(即,已经释放掉的存储空间)进行了操作。但是,相 比之下,f2()函数不会有任何问题。因为,new这个命令是在堆中申请存储空间,一旦申请成功,除非你将其delete或者程序终结,这块内存将一直存 在。也可以这样理解,堆内存是共享单元,能够被多个函数共同访问。如果你需要有多个数据返回却苦无办法,堆内存将是一个很好的选择。但是一定要避免下面的 事情发生:

  1: void f() 2: { 3: … 4: char * p; 5: p = (char*)new char[100]; 6: … 7: }

  这个程序做了一件很无意义并且会带来很大危害的事情。因为,虽然申请了堆内存,p保存了堆内存的首地址。但是,此变量是临时变量,当函数调用结 束时p变量消失。也就是说,再也没有变量存储这块堆内存的首地址,我们将永远无法再使用那块堆内存了。但是,这块堆内存却一直标识被你所使用(因为没有到 程序结束,你也没有将其delete,所以这块堆内存一直被标识拥有者是当前您的程序),进而其他进程或程序无法使用。我们将这种不道德的“流氓行为” (我们不用,却也不让别人使用)称为内存泄漏(memory leak)

  综合以上两个例子,我们可以总结一下堆与栈到底有哪些区别!

  (1)管理方式不同

  对于来讲,是由编译器自动管理,无需我们手工控制;对于来说,释放工作由程序员控制,容易产生memory leak。

  (2)空间大小不同

  空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于来讲,一般都是有一定的空间大小的,例如,在VC6.0下面默认的栈空间大小是1M,可以修改这个值。

  (3)能否产生碎片不同

  对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题, 因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。

  (4)生长方向不同

  对于来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。(详见第一部分的内存分配图)

  (5)分配方式不同

  都是动态分配的,没有静态分配的堆。有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

  (6)分配效率不同

  是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比 较高。则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆 内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多

  总结:

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

  虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。

  无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到 的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内存内存C++中有一些区别。内存是存储在计算机的RAM中的一块连续的内存区域,用于存储局部变量和函数调用的上下文信息。上的变量在其作用域结束时会自动释放,不需要手动释放。内存的分配和释放速度较快,但是大小受限于系统的大小。\[1\] 内存是一块不连续的内存区域,用于存储动态分配的数据。上的变量需要手动释放,否则可能会导致内存泄漏。内存的分配和释放速度较慢,可能会产生内存碎片。的大小受限于系统中有效的虚拟内存获得的空间比较灵活,也比较大,适用于需要动态分配大量内存或者在运行时无法确定需要多大内存的情况。\[2\]\[3\] 总结来说,内存适用于存储局部变量和函数调用的上下文信息,自动分配和释放,速度较快。而内存适用于动态分配的数据,需要手动分配和释放,速度较慢,但是空间灵活。 #### 引用[.reference_title] - *1* [什么是,它们在哪儿?](https://blog.csdn.net/Joey_zoe/article/details/38599505)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [的区别](https://blog.csdn.net/GeorgeDiDi/article/details/54908875)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值