zoukankan      html  css  js  c++  java
  • C/C++中编译程序的内存结构分布

    内存分配方式简介

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

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

    2、堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

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

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

    5、常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。关键字时const

    注意:

    1.在所有函数体外定义的是全局变量

    2.加了static修饰符后不管在哪里都存放在全局区(静态区)

    3.在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用

    4.在函数体内定义的static表示只在该函数体内有效

    5.函数中的常量字符串存放在常量区

    为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内存布局对调试程序非常有帮助,可以知道程序执行时,到底做了什么,有助于写出干净的代码。

     源文件转换为可执行文件

    源文件经过以下几步生成可执行文件:

     

    1、预处理(preprocessor):对#include、#define、#ifdef/#endif、#ifndef/#endif等进行处理

    2、编译(compiler):将源码编译为汇编代码

    3、汇编(assembler):将汇编代码汇编为目标代码

    4、链接(linker):将目标代码链接为可执行文件

    编译器和汇编器创建的目标文件包含:二进制代码(指令)、源码中的数据;链接器将多个目标文件链接成一个;装载器吧目标文件加载到内存。

    可执行程序组成及内存布局

    通过上面的小节,我们知道将源程序转换为可执行程序的步骤,典型的可执行文件分为两部分:

     

    代码段(Code),由机器指令组成,该部分是不可改的,编译之后就不再改变,放置在文本段(.text)。

    数据段(Data),它由以下几部分组:

    常量(constant),通常放置在只读read-only的文本段(.text)

    静态数据(static data),初始化的放置在数据段(.data);未初始化的放置在(.bss,Block Started by Symbol,BSS段的变量只有名称和大小却没有值)

    动态数据(dynamic data),这些数据存储在堆(heap)或栈(stack)

    源程序编译后链接到一个以0地址为始地址的线性或多维虚拟地址空间。而且每个进程都拥有这样一个空间,每个指令和数据都在这个虚拟地址空间拥有确定的地址,把这个地址称为虚拟地址(Virtual Address)。将进程中的目标代码、数据等的虚拟地址组成的虚拟空间称为虚拟存储器(Virtual Memory)。典型的虚拟存储器中有类似的布局:

     当进程被创建时,内核为其提供一块物理内存,将虚拟内存映射到物理内存,这些都是由操作系统来做的。

      数据存储类别

    讨论C/C++中的内存布局,不得不提的是数据的存储类别!数据在内存中的位置取决于它的存储类别。一个对象是内存的一个位置,解析这个对象依赖于两个属性:存储类别、数据类型。

    存储类别决定对象在内存中的生命周期。

    数据类型决定对象值的意义,在内存中占多大空间。

    C/C++中由(auto、 extern、 register、 static)存储类别和对象声明的上下文决定它的存储类别。

      1、自动对象(automatic objects)

    auto和register将声明的对象指定为自动存储类别。他们的作用域是局部的,诸如一个函数内,一个代码块{***}内等。操作了作用域,对象会被销毁。

    在一个代码块中声明一个对象,如果没有执行auto,那么默认是自动存储类别。

    声明为register的对象是自动存储类别,存储在计算机的快速寄存器中。不可以对register对象做取值操作“&”。

      2、静态对象(static objects)

    静态对象可以局部的,也可以是全局的。静态对象一直保持它的值,例如进入一个函数,函数中的静态对象仍保持上次调用时的值。包含静态对象的函数不是线程安全的、不可重入的,正是因为它具有“记忆”功能。

    局部对象声明为静态之后,将改变它在内存中保存的位置,由动态数据--->静态数据,即从堆或栈变为数据段或bbs段。

    全局对象声明为静态之后,而不会改变它在内存中保存的位置,仍然是在数据段或bbs段。但是static将改变它的作用域,即该对象仅在本源文件有效。此相反的关键字是extern,使用extern修饰或者什么都不带的全局对象的作用域是整个程序。

    C/C++中由源程序到可执行文件的步骤,和可执行程序的内存布局,数据存储类别,最后还通过一个例子来说明。可执行程序中的变量在内存中的布局可以总结为如下:

     

    变量(函数外):如果未初始化,则存放在BSS段;否则存放在data段

    变量(函数内):如果没有指定static修饰符,则存放在栈中;否则同上

    常量:存放在文本段.text

    函数参数:存放在栈或寄存器中

    内存可以分为以下几段:

     

    文本段:包含实际要执行的代码(机器指令)和常量。它通常是共享的,多个实例之间共享文本段。文本段是不可修改的。

    初始化数据段:包含程序已经初始化的全局变量,.data。

    未初始化数据段:包含程序未初始化的全局变量,.bbs。该段中的变量在执行之前初始化为0或NULL。

    栈:由系统管理,由高地址向低地址扩展。

    堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间,通过free、delete/delete[]释放所申请的

    空间。由低地址想高地址扩展

    堆和栈的比较

    申请方式

    stack: 由系统自动分配。

    heap: 需要程序员自己申请,并指明大小,在 C 中 用malloc 函数, C++ 中是 new 运算符。

    申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

    对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete 语句才能正确的释放本内存空间。

    由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

    申请大小的限制

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是 2M (也有的说是 1M ,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

    申请效率的比较

    栈由系统自动分配,速度较快。但程序员是无法控制的 。

    堆是由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片 , 不过用起来方便 。

    堆和栈中的存储内容

    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

    存取效率的比较  参考:https://blog.csdn.net/baidu_37964071/article/details/81428139

    一个由C/C++编译的程序占用的内存分为以下几个部分

    1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

    2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。

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

    4、常量存储区 —常量字符串就是放在这里的。 程序结束后由系统释放

    5、程序代码区—存放函数体的二进制代码。

    ————————————————

    //main.cpp 

    int a = 0; // 全局初始化区 

    char *p1; // 全局未初始化区 

    const int b = 10; // 变量将在常量存储区,程序执行的全程不能修改

    char * p2 = new char[9]; // 这个是声明的一个动态变量,将存放在动态存储区,在程序运行时分配内存

    // get dynamic memory:

    int main() 

    // 在这个代码块里定义的变量都是定义在了栈上,随着程序的结束这里的变量将被全部释放

    int b; // 栈 

    char s[] = "abc"; // 栈 

    char *p2; // 栈 

    char *p3 = "123456"; // 123456在常量区,p3在栈上。 

    static int c =0; // static关键字在哪里都可以让一个变量成为全局(静态)变量,初始化区 

    p1 = (char *)malloc(10); 

    p2 = (char *)malloc(20); 

    // 分配得来得10和20字节的区域就在堆区。 

    strcpy(p1, "123456"); // 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 

    return 0;

    }

    ————————————————

    编译其实只是一个扫描过程,进行词法语法检查,代码优化而已,编译程序越好,程序运行的时候越高效。

    我想你说的“编译时分配内存”是指“编译时赋初值”,它只是形成一个文本,检查无错误,并没有分配内存空间。类似一个占位符的检查,你在的位置对吗,这个位置该你占位嘛。

    当你运行时,系统才把程序导入内存。一个进程(即运行中的程序)在主要包括以下五个分区:

    栈、堆、bss、data、code

    代码(编译后的二进制代码)放在code区,代码中生成的各种变量、常量按不同类型分别存放在其它四个区。系统依照代码顺序执行,然后依照代码方案改变或调用数据,这就是一个程序的运行过程。

    运行时程序是必须调到“内存”的。因为CPU(其中有多个寄存器)只与内存打交道的。程序在进入实际内存之前要首先分配物理内存。

    编译

    编译器能够识别语法,数据类型等等。然后逐行逐句检查编译成二进制数据的obj文件,然后再由链接程序将其链接成一个EXE文件。此时的程序是以EXE文件的形式存放在磁盘上。

    运行

    当执行这个EXE文件以后,此程序就被加载到内存中,成为进程。此时一开始程序会初始化一些全局对象,然后找到入口函数(main()或者WinMain()),就开始按程序的执行语句开始执行。此时需要的内存只能在程序的堆上进行动态增加/释放了。

    参考:https://blog.csdn.net/qq_42570601/article/details/107598826

    https://blog.csdn.net/ct2008112101/article/details/38680829

    1.栈(stack):1.由编译器自动分配释放,存放函数的参数值,局部变量。函数被调用时用来传递参数和返回值

                          2.其他操作类似于数据结构中的栈

    2.堆(heap):存放动态分布的内存段当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆    上(堆  被 被扩张)/释放的内存从堆中被提出(堆被缩减)。堆一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

    3.全局数据区:全局变量和静态变量是放在一块,初始化后的全局变量和静态变量在一块区域,没有初始化的全局变量和静态变量在一块区域。文字常量和常量字符串在这块区域,程序结束后被系统释放

                            (1)BSS段:存放未初始化的全局变量

                               (2) 数据段:已初始化的全局变量

    4.代码段 :存放程序执行代码的一块区域。程序段为程序代码在内存中的映射,一个程序可以在内存中有多个副本

    注意:
    1. 在所有函数体外定义的是全局变量
    2. 加了static修饰符后不管在哪里都存放在全局区(静态区)
    3. 在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用
    4. 在函数体内定义的static表示只在该函数体内有效
    5. 函数中的常量字符串存放在常量区
  • 相关阅读:
    java OA系统 自定义表单 流程审批 电子印章 手写文字识别 电子签名 即时通讯
    flowable 获取当前任务流程图片的输入流
    最新 接口api插件 Swagger3 更新配置详解
    springboot 集成 activiti 流程引擎
    java 在线考试系统源码 springboot 在线教育 视频直播功能 支持手机端
    阿里 Nacos 注册中心 配置启动说明
    springboot 集成外部tomcat war包部署方式
    java 监听 redis 过期事件
    springcloudalibaba 组件版本关系
    java WebSocket 即时通讯配置使用说明
  • 原文地址:https://www.cnblogs.com/klb561/p/14863144.html
Copyright © 2011-2022 走看看