C++ 系列:内存布局

 

 转载自http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.htmlhtml

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

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

一、源文件转换为可执行文件函数

源文件通过如下几步生成可执行文件:布局

  • 一、预处理(preprocessor):对#include、#define、#ifdef/#endif、#ifndef/#endif等进行处理
  • 二、编译(compiler):将源码编译为汇编代码
  • 三、汇编(assembler):将汇编代码汇编为目标代码
  • 四、连接(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)。典型的虚拟存储器中有相似的布局:htm

  • 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.1 自动对象(automatic objects)

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

  • 在一个代码块中声明一个对象,若是没有执行auto,那么默认是自动存储类别。
  • 声明为register的对象是自动存储类别,存储在计算机的快速寄存器中。不能够对register对象作取值操做“&”。

1.2 静态对象(static objects)

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

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

 

四、一个实例

下面咱们分析一段代码:

#include <stdio.h>

#include <stdlib.h>

 

int a;

static int b;

void func()

{

    char c;

    static int d;

}

 

 

int main()

{

    int e;

    int* pi = (int*)malloc(sizeof(int));

    func();

    func();

   

    free(pi);

 

    return 0;

}

程序中声明的变量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[]释放所申请的空间。由低地址想高地址扩展。
相关文章
相关标签/搜索