• C++编译过程与内存空间


    

    为什么须要知道C/C++的内存布局和在哪能够能够找到想要的数据?知道内存布局对调试程序很有帮助,能够知道程序运行时,究竟做了什么,有助于写出干净的代码。本文的主要内容例如以下:

    • 源文件转换为可运行文件
    • 可运行程序组成及内存布局
    • 数据存储类别
    • 一个实例
    • 总结

      源文件转换为可运行文件

    源文件经过下面几步生成可运行文件:

    • 1、预处理(preprocessor):对#include、#define、#ifdef/#endif、#ifndef/#endif等进行处理
    • 2、编译(compiler):将源代码编译为汇编代码
    • 3、汇编(assembler):将汇编代码汇编为目标代码
    • 4、链接(linker):将目标代码链接为可运行文件

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

    图1 源文件到可运行文件的步骤

     

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

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

    • 代码段(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)。典型的虚拟存储器中有类似的布局:

    • Text Segment (.text)
    • Initialized Data Segment (.data)
    • Uninitialized Data Segment (.bss)
    • The Stack
    • The Heap

    图2 进程内存布局

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

      数据存储类别

    讨论C/C++中的内存布局。不得不提的是数据的存储类别!

    数据在内存中的位置取决于它的存储类别。一个对象是内存的一个位置。解析这个对象依赖于两个属性:存储类别、数据类型。

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

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

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

      1、自己主动对象(automatic objects)

    autoregister将声明的对象指定为自己主动存储类别。他们的作用域是局部的,诸如一个函数内,一个代码块{***}内等。

    操作了作用域,对象会被销毁。

    • 在一个代码块中声明一个对象。假设没有运行auto,那么默认是自己主动存储类别。
    • 声明为register的对象是自己主动存储类别,存储在计算机的高速寄存器中。

      不能够对register对象做取值操作“&”。

      2、静态对象(static objects)

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

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

     

      一个实例

    以下我们分析一段代码:

     

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3.    
    4. int a;  
    5. static int b;  
    6. void func(void)  
    7. {  
    8.     charc;  
    9.     static int d;  
    10. }  
    11. int main(void)  
    12. {  
    13.     int e;  
    14.     int*pi = ( int*)malloc(sizeof(int));  
    15.     func ();  
    16.     func ();  
    17.     free(pi );  
    18.     return(0);  
    19. }  


     

    ?

    程序中声明的变量a、b、c、d、e、pi的存储类别和生命期例如以下所述:

    • a是一个未初始化的全局变量。作用域为整个程序,生命期是整个程序执行期间,在内存的bbs段
    • b是一个未初始化的静态全局变量,作用域为本源文件。生命期是整个程序执行期间,在内存的bbs段
    • c是一个未初始化的局部变量,作用域为函数func体内。即仅在函数体内可见。生命期也是函数体内。在内存的栈中
    • d是一个未初始化的静态局部变量,作用域为函数func体内。即仅在函数体内可见,生命期是整个程序执行期间,在内存的bbs段
    • e是一个未初始化的局部变量。作用域为函数main体内。即仅在函数体内可见,生命期是main函数内,在内存的栈中
    • pi是一个局部指针,指向堆中的一块内存块,该块的大小为sizeof(int),pi本身存储在内存的栈中,生命期是main函数内
    • 新申请的内存块在堆中,生命期是malloc/free之间

    用图表演示样例如以下:

    图3 样例的内存布局

     

      总结

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

    • 变量(函数外):假设未初始化。则存放在BSS段;否则存放在data段
    • 变量(函数内):假设没有指定static修饰符。则存放在栈中;否则同上
    • 常量:存放在文本段.text
    • 函数參数:存放在栈或寄存器中

    内存能够分为下面几段:

    • 文本段:包括实际要运行的代码(机器指令)和常量。它一般是共享的。多个实例之间共享文本段。文本段是不可改动的。
    • 初始化数据段:包括程序已经初始化的全局变量,.data。
    • 未初始化数据段:包括程序未初始化的全局变量。.bbs。

      该段中的变量在运行之前初始化为0或NULL。

    • 栈:由系统管理,由高地址向低地址扩展。
    • 堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间。通过free、delete/delete[]释放所申请的
    • 空间。由低地址想高地址扩展
    
    
  • 相关阅读:
    hdu 1240 Asteroids!
    hdu 1253 胜利大逃亡
    hdu 1035 Robot Motion
    hdu 1181 变形课
    hdu 1548 A strange lift
    DFS Sum It Up
    hdu 3278 Catch That Cow
    hdu 1312 Red and Black
    ACM菜鸟
    eclipse新建安卓项目点击finish后窗口无法关闭
  • 原文地址:https://www.cnblogs.com/lxjshuju/p/7257005.html
Copyright © 2020-2023  润新知