c/c++中内存区域划分大总结

c/c++中内存区域划分大总结 
标签: 杂谈


  3. 全局区(静态区), 全局变量和静态变量的存储是放在一块的, 初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 -程序结束释放


  4. 另外还有一个专门放常量的地方。 -cheng序结束释放


  5. 程序代码区, 存放2进制代码。


  在函数体中定义的变量通常是在栈上, 用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。 在所有函数体外定义的是全局量, 加了static修饰符后不论在哪里都存放在全局区(静态区), 在所有函数体外定义的static变量表示在该文件中有效, 不能extern到别的文件用, 在函数体内定义的static表示只在该函数体内有效。 另外, 函数中的adgfdf这样的字符串存放在常量区。 比如:


  二、在c++中, 内存分成5个区, 他们区分是堆、栈、自在存储区、全局/静态存储区和常量存储区


  1. 栈, 就是那些由编译器在需要的时候分配, 在不需要的时候自动清楚的变量的存储区。 外面的变量通常是部分变量、函数参数等。


  2. 堆, 就是那些由new分配的内存块, 他们的释放编译器不去管, 由我们的运用程序去控制, 普通一个new就要对应一个delete. 如果程序员没有释放掉, 那么在程序结束后,


  操作系统会自动回收。


  3. 自在存储区, 就是那些由malloc等分配的内存块, 他和堆是十分相似的, 不过它是用free来结束自己的生命的。


  4. 全局/静态存储区, 全局变量和静态变量被分配到同一块内存中, 在以前的c言语中, 全局变量又分为初始化的和未初始化的, 在c++外面没有这个区分了, 他们共同占用同一块内存区。


  5. 常量存储区, 这是一块比拟特殊的存储区, 他们外面存放的是常量, 不允许修正(当然, 你要经过非正当手段也可以修正)


  三、谈谈堆与栈的关系与区别


  具体地说, 现代计算机(串行执行机制), 都直接在代码底层支持栈的数据结构。 这表现在, 有专门的寄存器指向栈所在的地址, 有专门的机器指令完成数据入栈出栈的操作。 这种机制的特点是效率高, 支持的数据无限, 普通是整数, 指针, 浮点数等系统直接支持的数据类型, 并不直接支持其他的数据结构。 由于栈的这种特点, 对栈的运用在程序中是十分频繁的。 对子程序的调用就是直接利用栈完成的。 机器的call指令里隐含了把返回地址推入栈, 然后跳转至子程序地址的操作, 而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。 c/c++中的自动变量是直接利用栈的例子, 这也就是为什么当函数返回时, 该函数的自动变量自动生效的原因。


  和栈不同, 堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的, 而是由函数库提供的。 基本的malloc/realloc/free函数维护了一套内部的堆数据结构。 当程序运用这些函数去取得新的内存空间时, 这套函数首先试图从内部堆中寻找可用的内存空间, 如果没有可以运用的内存空间, 则试图利用系统调用来静态增加程序数据段的内存大小?路峙涞玫降目占涫紫缺蛔橹诓慷阎腥ィ?然后再以适当的方式返回给调用者。 当程序释放分配的内存空间时, 这片内存空间被返回内部堆结构中, 可能会被适当的处理(比如和其他空闲空间兼并成更大的空闲空间), 以更适合下一次内存分配请求。 这套复杂的分配机制实际上相当于一个内存分配的缓冲池(cache), 运用这套机制有如下若干原因:


  1. 系统调用可能不支持恣意大小的内存分配。 有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于少量的小内存分类来说会造成浪费。


  2. 系统调用请求内存可能是代价昂贵的。 系统调用可能涉及用户态和中心态的转换。


  3. 没有管理的内存分配在少量复杂内存的分配释放操作下很容易造成内存碎片。


  堆和栈的对比


  从以上知识可知, 栈是系统提供的功用, 特点是疾速高效, 缺陷是无限制, 数据不灵活;而栈是函数库提供的功用, 特点是灵活方便, 数据适应面普遍, 但是效率有一定降低。 栈是系统数据结构, 对于进程/线程是独一的;堆是函数库内部数据结构, 不一定独一。 不同堆分配的内存无法互相操作。 栈空间分静态分配和静态分配两种。 静态分配是编译器完成的, 比如自动变量(auto)的分配。 静态分配由alloca函数完成。 栈的静态分配无需释放(是自动的), 也就没有释放函数。 为可移植的程序起见, 栈的静态分配操作是不被鼓励的!堆空间的分配总是静态的, 虽然程序结束时所有的数据空间都会被释放回系统, 但是准确的请求内存/释放内存匹配是良好程序的基本要素。


  1. 碎片成果:对于堆来讲, 频繁的new/delete势必会造成内存空间的不延续, 从而造成少量的碎片, 使程序效率降低。 对于栈来讲, 则不会存在这个成果, 由于栈是先进后出的队列, 他们是如此的逐一对应, 以至于永远都不可能有一个内存块从栈中间弹出, 在他弹出之前, 在他下面的后进的栈内容曾经被弹出, 详细的可以>参考数据结构, 这里我们就不再逐一讨论了。


  2. 生长方向:对于堆来讲, 生长方向是向上的, 也就是向着内存地址增加的方向;对于栈来讲, 它的生长方向是向下的, 是向着内存地址减小的方向增长。


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


  4. 分配效率:栈是机器系统提供的数据结构, 计算时机在底层对栈提供支持:分配专门的寄存器存放栈的地址, 压栈出栈都有专门的指令执行, 这就决议了栈的效率比拟高。 堆则是c/c++函数库提供的, 它的机制是很复杂的, 例如为了分配一块内存, 库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存


  中搜索可用的足够大小的空间, 如果没有足够大小的空间(可能是由于内存碎片太多), 就有可能调用系统功用去增加程序数据段的内存空间, 这样就无时机分到足够大小的内存, 然后停止返回。 显然, 堆的效率比栈要低得多。


  明白区分堆与栈:


  在bbs上, 堆与栈的区分成果, 似乎是一个永恒的话题, 由此可见, 初学者对此往往是混杂不清的, 所以我决议拿他第一个开刀。


  首先, 我们举一个例子:


  这条短短的一句话就包括了堆与栈, 看到new, 我们首先就应该想到, 我们分配了一块堆内存, 那么指针p呢?他分配的是一块栈内存, 所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p. 在程序会先确定在堆中分配内存的大小?缓蟮饔胦peratornew分配内存, 然后返回这块内存的首地址, 放入栈中, 他在vc6下的汇编代码如下:


  这里, 我们为了复杂并没有释放内存, 那么该怎么去释放呢?是deletep么?澳, 错了, 应该是delete[]p, 这是为了告诉编译器:我删除的是一个数组, vc6就会根据相应的cookie信息去停止释放内存的工作。


  好了, 我们回到我们的主题:堆和栈终究有什么区别?


  C言语主要的区别由以下几点:


  1、管理方式不同;


  2、空间大小不同;


  3、能否发生碎片不同;


  4、生长方向不同;


  5、分配方式不同;


  6、分配效率不同;


  管理方式:对于栈来讲, 是由编译器自动管理, 无需我们手工控制;对于堆来说, 释放工作由程序员控制, 容易发生memoryleak.


  空间大小?浩胀ɡ唇苍?2位系统下, 堆内存可以达到4g的空间, 从这个角度来看堆内存几乎是没有什么限制的。 但是对于栈来讲, 普通都是有一定的空间大小的, 例如, 在vc6下面, 默认的栈空间大小是1m(仿佛是, 记不清楚了)。 当然, 我们可以修正:


  翻开工程, 顺次操作菜单如下:project->setting->link, 在category中选中output, 然后在reserve中设定堆栈的最大值和commit. 留意:reserve最小值为4byte;commit是保留在虚拟内存的页文件外面, 它设置的较大会使栈开辟较大的值, 可能增加内存的开销和启动时间。


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


  另外对存取效率的比拟:代码:


  但是, 在当前的存取中, 在栈上的数组比指针所指向的字符串(例如堆)快。


  比如:


  对应的汇编代码


  第一种在读取时直接就把字符串中的元素读到寄存器cl中, 而第二种则要先把指针值读到edx中, 在根据edx读取字符, 显然慢了。


  无论是堆还是栈, 都要防止越界景象的发作(除非你是故意使其越界), 由于越界的结果要么是程序解体, 要么是摧毁程序的堆、栈结构, 发生以想不到的结果, 就算是在你的程序运行过程中, 没有发作下面的成果, 你还是要小心, 说不定什么时候就崩掉, 编写动摇平安的代码才是最重要的。


  static用来控制变量的存储方式和可见性


  函数内部定义的变量, 在程序执行到它的定义处时, 编译器为它在栈上分配空间, 函数在栈上分配的空间在此函数执行结束时会释放掉, 这样就发生了一个成果:如果想将函数中此变量的值保管至下一次调用时, 如何完成?最容易想到的方法是定义一个全局的变量, 但定义为一个全局变量有许多缺陷, 最明显的缺陷是破坏了此变量的访问范围(使得在此函数中定义的变量, 不只仅受此函数控制)。


  需要一个数据对象为整个类而非某个对象服务, 同时又力图不破坏类的


  封装性, 即要求此成员隐藏在类的内部, 对外不可见。


  静态数据成员要在程序一开始运行时就必须存在。 由于函数在程序运行中被调用, 所以静态数据成员不能在任何函数内分配空间和初始化。


  这样, 它的空间分配有三个可能的地方, 一是作为类的外部接口的头文件, 那里有类声明;二是类定义的内部完成, 那里有类的成员函数定义;三是运用程序的main()函数前的全局数据声明和定义处。


  静态数据成员要实际地分配空间, 故不能在类的声明中定义(只能声明数据成员)。 类声明只声明一个类的“尺寸和规格”, 并不停止实际的内存分配, 所以在类声明中写成定义是错误的。 它也不能在头文件中类声明的外部定义, 由于那会造成在多个运用该类的源文件中, 对其重复定义。


  static被引入以告知编译器, 将变量存储在程序的静态存储区而非栈上空间, 静态数据成员按定义呈现的先后顺序顺次初始化, 留意静态成员嵌套时, 要保证所嵌套的成员曾经初始化了。 消除时的顺序是初始化的反顺序。


  可以节省内存, 由于它是所有对象所私有的, 因此, 对多个对象来说, 静态数据成员只存储一处, 供所有对象共用。 静态数据成员的值对每个对象都是一样, 但它的值是可以更新的。 只需对静态数据成员的值更新一次, 保证所有对象存取更新后的相同的值, 这样可以提高时间效率。


  援用静态数据成员时, 采用如下格式:


  如果静态数据成员的访问权限允许的话(即public的成员), 可在程序中, 按上述格式来援用静态数据成员。


  (1)类的静态成员函数是属于整个类而非类的对象, 所以它没有this指针, 这就招致了它仅能访问类的静态数据和静态成员函数。


  (2)不能将静态成员函数定义为虚函数。


  (3)由于静态成员声明于类中, 操作于其外, 所以对其取地址操作, 就多少有些特殊, 变量地址是指向其数据类型的指针, 函数地址类型是一个“nonmember函数指针”。


  (4)由于静态成员函数没有this指针, 所以就差不多同等于nonmember函数, 结果就发生了一个意想不到的益处:成为一个callback函数, 使得我们得以将c++和c-basedxwindow系统结合, 同时也成功的运用于线程函数身上。


  (5)static并没有增加程序的时空开销, 相反她还缩短了子类对父类静态成员的访问时间, 节省了子类的内存空间。


  (6)静态数据成员在<定义或阐明>时后面加关键字static.


  (7)静态数据成员是静态存储的, 所以必须对它停止初始化。


  (8)静态成员初始化与普通数据成员初始化不同:


  初始化在类体外停止, 而后面不加static, 以免与普通静态变量或对象相混杂;初始化时不加该成员的访问权限控制符private, public等;


  初始化时运用作用域运算符来标明它所属类;


  所以我们得出静态数据成员初始化的格式:


  (9)为了防止父类的影响, 可以在子类定义一个与父类相同的静态变量, 以屏蔽父类的影响。 这里有一点需要留意:我们说静态成员为父类和子类共享, 但我们有重复定义了静态成员, 这会不会惹起错误呢?不会, 我们的编译器采用了一种绝妙的手法:name-mangling用以生成独一的标志。
文章由颈椎保健枕整理,收集辛苦,希望能保留出处。

你可能感兴趣的:(c/c++中内存区域划分大总结)