`
v5browser
  • 浏览: 1137200 次
社区版块
存档分类
最新评论

C/C++的堆栈,内存分配

 
阅读更多

一、一个经过编译的C/C++的程序占用的内存分成以下几个部分:
1、栈区(stack):

由编译器自动分配和释放 ,存放函数的参数值、局部变量的值等,甚至函数的调用过程都是用栈来完成。其操作方式类似于数据结构中的栈。


2、堆区(heap) :

一般由程序员手动申请以及释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。


3、全局区(静态区)(static):

全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放空间。


4、文字常量区:

常量字符串就是放在这里的。 程序结束后由系统释放空间。


5、程序代码区:

存放函数体的二进制代码。

二、内存段式管理:代码段、数据段、堆栈段

目前通用系统(Linux或者Window)多采用段页式内存管理方式,即段式管理与页式管理的组合。每个程序的内存空间分为若干段,进行内存的分配与释放管理;每个段又分为若干页,进行虚拟页与实际页面的映射。段式管理程序以段为单位分配内存,依赖地址映射机制完成段式虚拟地址与实际内存地址的转换。段式管理(程序分段思想)在无OS的嵌入式开发和微系统开发中具有广泛的应用。

程序内存一般分为代码段、数据段和堆栈段。

代码段(code/text segment):用来存放执行代码的一块内存区域。该区域的大小在程序运行前就已经确定,并且内存区域属于只读。其中可能包含一些只读的常数变量(如字符串常量)。

数据段(data segment):用来存放数据的一块内存区域,分成初始化为非零的数据区BSS(Heap)三个区域。初始化非零数据区域一般存放静态非零数据和全局的非零数据,属于静态内存分配;BSS(Block Started by Symbol)区域一般存放未初始化的全局数据和静态数据,属于静态内存分配;堆区域一般存放运行时动态分配的内存空间,其大小不固定,可动态扩张或缩减。当调用malloc等函数分配内存时,新分配的内存被动态添加到堆上;当调用free等函数释放内存时,被释放内存从堆中被剔除。

堆栈段(stack segment):亦称栈,用于存放程序临时创建的局部变量和函数参数,属于动态内存分配。

代码段和数据段之间有明确的分隔,但是数据段和堆栈段之间没有,而且栈是向下增长,堆是向上增长的,因此理论上堆和栈会“增长到一起”,操作系统的内存管理功能需要防止这样的错误发生。



下面的例子可以完全展示不同的变量所占的内存区域:

//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; //栈中
char s[] = "abc"; //栈中
char *p2; //栈中
char *p3 = "123456"; //123456/0在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
//以下分配得到的10和20字节的区域就在堆区
p1 = (char *)malloc(10);
p2 = new char[20];//(char *)malloc(20);
strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}


三、栈(stack)和堆(heap)具体的区别
1、在申请方式上
栈(stack): 现在很多人都称之为堆栈,这个时候实际上还是指的栈。它由编译器自动管理,无需我们手工控制。 例如,声明函数中的一个局部变量 int b 系统自动在栈中为b开辟空间;在调用一个函数时,系统自动的给函数的形参变量在栈中开辟空间。
堆(heap): 申请和释放由程序员控制,并指明大小。容易产生memory leak。
在C中使用malloc函数。
如:p1 = (char *)malloc(10);
在C++中用new运算符。
如:p2 = new char[20];//(char *)malloc(10);
但是注意p1本身在全局区,而p2本身是在栈中的,只是它们指向的空间是在堆中。

2、申请后系统的响应上
栈(stack):只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆(heap): 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete或 free语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

3、申请大小的限制
栈(stack):在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

堆(heap): 堆是向高地址扩展的数据结构,是不连续的内存区域(空闲部分用链表串联起来)。正是由于系统是用链表来存储空闲内存,自然是不连续的,而链表的遍历方向是由低地址向高地址。一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。由此可见,堆获得的空间比较灵活,也比较大。

4、分配空间的效率上
栈(stack):栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。但程序员无法对其进行控制。
堆(heap):是C/C++函数库提供的,由new或malloc分配的内存,一般速度比较慢,而且容易产生内存碎片。它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。这样可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。显然,堆的效率比栈要低得多。

5、堆和栈中的存储内容
栈(stack):在函数调用时,第一个进栈的是主函数中子函数调用后的下一条指令(子函数调用语句的下一条可执行语句)的地址,然后是子函数的各个形参。在大多数的C编译器中,参数是由右往左入栈的,然后是子函数中的局部变量。注意:静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中子函数调用完成的下一条指令,程序由该点继续运行。
堆(heap):一般是在堆的头部用一个字节存放堆的大小,堆中的具体内容有程序员安排。

6、存取效率的比较
这个应该是显而易见的。拿栈上的数组和堆上的数组来说:

void main()
{
int arr[5]={1,2,3,4,5};
int *arr1;
arr1=new int[5];
for (int j=0;j<=4;j++)
{
arr1[j]=j+6;
}
int a=arr[1];
int b=arr1[1];
}
上面代码中,arr1(局部变量)是在栈中,但是指向的空间确在堆上,两者的存取效率,当然是arr高。因为arr[1]可以直接访问,但是访问arr1[1],首先要访问数组的起始地址arr1,然后才能访问到arr1[1]。

总而言之,言而总之:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(声明变量)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。


文章出处:http://www.diybl.com/course/3_program/c++/cppsl/20081120/152130.html

补充:

1.new、delete、malloc、free关系
delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

2.delete与 delete []区别
delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。在More Effective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operatordelete来释放内存。”delete与New配套,delete []与new []配套
MemTest*mTest1=newMemTest[10];
MemTest*mTest2=newMemTest;
int*pInt1=newint[10];
int*pInt2=newint;
delete[]pInt1; //-1-
delete[]pInt2; //-2-
delete[]mTest1;//-3-
delete[]mTest2;//-4-
在-4-处报错。


这就说明:对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。 delete[]删除一个数组,delete删除一个指针简单来说,用new分配的内存用delete删除用new[]分配的内存用delete[]删除 delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

-----------------------------------------------------------------------------------------------------------

常见的内存分配和使用错误:
1) 内存的申请和分配并没有成功,但程序员却使用了它。
判断指针的值是否为NULL可以有效地避免这种错误。

2) 内存的分配已经成功,但是却没有进行初始化就直接使用它了。
主观地认为自己申请的内存的缺省值为0,这样想是没有什么道理的,内存分配后的值是不确定的。

3) 申请了内存,使用完了却忘记了释放,导致内存泄露。它会慢慢地吞噬你的系统资源,直到你的程序彻底完蛋。

4) 你很小心地释放了内存,但是却又使用了它。由于程序很复杂或者调用顺序出错,这样可能导致出现上面的错误。


-----------------------------------------------------------------------------------------------------------

C#的值类型内存分配总是和上下文有关,而不总是在堆栈上

总是很常见到一些说法是值类型总是分配在堆栈上,引用类型总是分配在堆上(google搜索大约10万条记录- -)

最近仔细思考了一下发现有点问题....当然我个人水平有限, 有什么差错还请大家指正

个人总结的c#值类型和应用类型的分配应该是:

  应用类型肯定在托管堆上,值类型总是和上下文有关

1.类的实例成员, 类是引用类型,总是分配在堆上,那么a的内存就在ClassA的实例的内存里,也必然在堆上

class ClassA
{
int a;
}

2.类的静态成员,静态成员a总是在Typeof(ClassA)的内存里,那么也必然在堆上

PS:每个类必然有一个定义类方法,类静态成员等的Type对象,一个类可能可能有无数个实例,当永远只有一个Type定义,这也就是为什么静态构造函数也叫类型构造函数,并且永远只执行一次的原因

class ClassA
{
static int a;
}

3.结构体的静态和实例值类型成员和结构体所在的上下文有关

4.参数 Parameter,参数变量的内存总是位于执行堆栈上

void Test(int i)
{
Console.WriteLine(i);
}

5.本地变量(非闭包),参数变量的内存总是位于执行堆栈上

void Test()
{
int i = 100;
Console.WriteLine(i);
}

6.本地变量(闭包),dotnet会将lambda表达式和匿名委托编译为一个类,由于这个类中使用到了i1,本地变量i1将被编译到这个类里面,类的实例成员自然是在堆上啦

static void Main(string[] args)
{
int i1 = 1;
Func<int> f = () => { return i1; };
}

这里的堆栈:指的是执行堆栈

这里的堆:托管堆

分享到:
评论

相关推荐

    C/C++程序员面试指南.杨国祥(带详细书签).pdf

    面试题18:简述C、C++程序编译的内存分配情况 面试题19:以下四段代码中哪段没有错误 第6章 字符串 6.1 数字字符串 面试题1:编码实现数字转化为字符串 面试题2:编码实现字符串转化为数字 6.2 字符串函数 面试题3:...

    c/c++堆栈分布及其设置方法

    一个由C/C++编译的程序占用的内存分为以下几个部分1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数名,局部变量的名等。其操作方式类似于数据结构中的栈。2、堆区(heap)— 由程序员分配释放, 若程序员...

    C/C++笔试题(附答案,华为面试题系列)

    答:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变 量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调 用C 函数。C++提供了一个C 连接交换指定符号...

    TinyUSB是嵌入式系统的开源跨平台USB主机/设备堆栈-C/C++开发

    TinyUSB是面向嵌入式系统的开源跨平台USB主机/设备堆栈,旨在实现无动态分配的内存安全和延迟所有中断事件的线程安全,然后在非ISR任务功能中进行处理。 TinyUSB TinyUSB是用于嵌入式系统的开源跨平台USB主机/设备...

    C/C++程序内存的各种变量存储区域和各个区域详解

    知道如上一些内存分配机制,有助于我们理解指针的概念。C/C++不提供垃圾回收机制,因此需要对堆中的数据进行及时销毁,防止内存泄漏,使用free和delete销毁new和malloc申请的堆内存,而栈内存是动态释放。全局变量、...

    C/C++中的内存管理小结

    前言 我们最初熟知的内存开辟方式: int val = 20: 在栈空间上开辟4个字节 ...这篇博客就来带大家梳理一下C/C++中的内存管理。 一:C/C++内存分布 对内存分段是计算机的管理机制 1.栈又叫堆栈,存放非静态局部变量

    用于C单元测试的微小且幼稚的内存分配器-C/C++开发

    Tiny:可测试的C内存分配器概述Tiny是一种小型且可移植的内存分配器。 它允许单元测试在运行时控制和查询分配器。 Tiny使用了非常幼稚的分配算法,这很可能不适合生产环境,但是非常可预测且易于检查。 特点体积小巧...

    c/c++ 学习总结 初学者必备

    栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。...

    C/C++ 中堆和栈及静态数据区详解

    C/C++ 中堆和栈及静态数据区详解  五大内存分区  在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。下面分别来介绍:  栈,就是那些由编译器在需要的时候分配,在不需要的...

    C和C++头文件对比一览

    #include &lt;stdlib.h&gt; //定义杂项函数及内存分配函数 #include &lt;string.h&gt; //字符串处理 #include &lt;strstrea.h&gt; //基于数组的输入/输出 #include &lt;time.h&gt; //定义关于时间的函数 #include &lt;wchar.h&gt; //宽字符...

    关于堆栈、静态、动态内存的理解

    预备知识—程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 堆区(heap) — 一般由...

    本人精心收集,c++头文件一览

     //定义杂项函数及内存分配函数 #include &lt;string.h&gt; //字符串处理 #include &lt;strstrea.h&gt; //基于数组的输入/输出 #include &lt;time.h&gt; //定义关于时间的函数 #include &lt;wchar.h&gt; //宽...

    C语言 动态内存分配详解

    C语言 动态内存分配详解 动态内存分配涉及到堆栈的概念:堆栈是两种数据结构。堆栈都是数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。 栈(操作系统):由操作系统自动分配释放 ,...

    堆内存和栈内存详解 ESP

    一、预备知识—程序的内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈 2、堆区(heap) ...

    vld(Visual Leak Detector 内存泄露检测工具 源码)

    但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分配,则可能无法检测到。Visual Leak Detector使用了C/C++提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始...

    c++中堆栈及创建对象示例代码

    堆(heap),堆包含一个链表来维护已用和空闲的不连续的内存块,存放在二级缓存中,一般由程序员分配释放。 快速记忆方式: 一级缓存比二级缓存快,栈是一个先进后出列表,存取非常快,所以栈是在一级缓存中。 栈中...

    函数调用与堆栈

    自动生存期:局部变量和函数形参一般都具有自动生存期,它们的内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时内存被收回。 动态生存期:具有动态生存期的变量的生存...

    vld-2.3-资源泄漏检测

    由于C/C++语言没有所谓的垃圾收集器,内存的分配和释放都需要程序员自己来控制,这会给C/C++程序员带来一定的困难。当您的程序越来越复杂时,它的内存管理也会变得越来越困难。内存泄漏、内存越界是最常见的内存问题...

    程序中堆栈定义

    学习C或C++编程时,对于堆和栈的使用说明。以及程序运行时内存的申请和释放关系进行说明。

    C++堆和栈的区别与联系讲解

    C++中,内存分为5个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。... 全局/静态存储区:全局变量和静态变量被分配到同一块内存中(在C语言中,全局变量又分为初始化的和未初始化的,C++中没有这

Global site tag (gtag.js) - Google Analytics