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

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

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

1、源文件转换为可执行文件

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

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

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

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

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

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

  • 代码段(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 进程内存布局

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

3、数据存储类别

讨论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修饰或者什么都不带的全局对象的作用域是整个程序。

4、一个实例

下面我们分析一段代码:

#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 例子的内存布局

5、总结

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

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

内存可以分为以下几段:

  • 文本段:包含实际要执行的代码(机器指令)和常量。它通常是共享的,多个实例之间共享文本段。文本段是不可修改的。
  • 初始化数据段:包含程序已经初始化的全局变量,.data。
  • 未初始化数据段:包含程序未初始化的全局变量,.bbs。该段中的变量在执行之前初始化为0或NULL。
  • 栈:由系统管理,由高地址向低地址扩展。
  • 堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间,通过free、delete/delete[]释放所申请的空间。由低地址想高地址扩展。

C++ 系列:内存布局的更多相关文章

  1. 好文章系列C/C++——图说C++对象模型:对象内存布局详解

    注:收藏好文章,得出自己的笔记,以查漏补缺!     ------>原文链接:http://blog.jobbole.com/101583/ 前言 本文可加深对C++对象的内存布局.虚表指针.虚 ...

  2. C++ 系列:C++ 内存布局

    1 前言 了解你所使用的编程语言究竟是如何实现的,对于C++程序员可能特别有意义.首先,它可以去除我们对于所使用语言的神秘感,使我们不至于对于编译器干的活感到完全不可思议:尤其重要的是,它使我们在De ...

  3. JVM 系列(4)一看就懂的对象内存布局

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  4. linux系统进程的内存布局

    内存管理模块是操作系统的心脏:它对应用程序和系统管理非常重要.今后的几篇文章中,我将着眼于实际的内存问题,但也不避讳其中的技术内幕.由于不少概念是通用的,所以文中大部分例子取自32位x86平台的Lin ...

  5. Anatomy of a Program in Memory.剖析程序的内存布局

    原文标题:Anatomy of a Program in Memory 原文地址:http://duartes.org/gustavo/blog/ [注:本人水平有限,只好挑一些国外高手的精彩文章翻译 ...

  6. UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

    在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境. 本章我们将了解一下的内容: 程序运行时,main函数是如何被调用的: 命令行参数是如何被传入到程序中的: 一个典型的内存布局是怎样的: ...

  7. 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?

    本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...

  8. HotSpot源码分析之C++对象的内存布局

    HotSpot采用了OOP-Klass模型来描述Java类和对象.OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象的具体类型.为了更好理解这个模型, ...

  9. 理解Java对象:要从内存布局及底层机制说起,话说….

    前言 大家好,又见面了,今天是JVM专题的第二篇文章,在上一篇文章中我们说了Java的类和对象在JVM中的存储方式,并使用HSDB进行佐证,没有看过上一篇文章的小伙伴可以点这里:<类和对象在JV ...

随机推荐

  1. Eclipse Android环境搭建

    1.先安装ADT,记得断开网络2.然后关闭Eclipse安装SDK,例如安装在C:\Users\Administrator\AppData\Local\Android\android-sdk3.重启E ...

  2. github怎么退出组织和删除自己创建的组织

    1. 点击头像,进入settings 2. 点击左侧菜单中的 Organizations 切换到Origanizations后,右侧面板中会出现所有的oragnizations,我这里只有一个,是我自 ...

  3. [Algorithm] 机器学习算法常用指标总结

    考虑一个二分问题,即将实例分成正类(positive)或负类(negative).对一个二分问题来说,会出现四种情况.如果一个实例是正类并且也被 预测成正类,即为真正类(True positive), ...

  4. python调用其他程序或脚本方法(转)

    python运行(调用)其他程序或脚本 在Python中可以方便地使用os模块运行其他的脚本或者程序,这样就可以在脚本中直接使用其他脚本,或者程序提供的功能,而不必再次编写实现该功能的代码.为了更好地 ...

  5. 几个常用的Linux命令

    最近在学习Linux,记录了几个命令如下: 首先认识:关机命令,我喜欢用的是:shutdown -h now ,当然还有init 0等命令(用自己习惯的);重启命令:reboot;注销命令:logou ...

  6. 转:aliyun阿里云Maven仓库地址——加速你的maven构建

    maven仓库用过的人都知道,国内有多么的悲催.还好有比较好用的镜像可以使用,尽快记录下来.速度提升100倍. http://maven.aliyun.com/nexus/#view-reposito ...

  7. Javascript模块化编程(三):require.js的用法

    Javascript模块化编程(三):require.js的用法 原文地址:http://www.ruanyifeng.com/blog/2012/11/require_js.html 作者: 阮一峰 ...

  8. 【转】linux 设置用户id 设置组id

    linux 设置用户id 设置组id   转自 linux 设置用户id 设置组id   最近看apue,简单记录对设置用户id理解(设置组id同理). 1. 相关的id好像很多,共有哪些? 文件2个 ...

  9. iOS6_自动约束 Constraints

    取消Constraints(约束) 问题描述:xib文件设计的时候控件已经摆好位置,但是每次调试的时候控件的位置又乱了 解决方法:选中 xib文件,在右侧第一项(Identity and type)的 ...

  10. sudo:有效用户 ID 不是 0,sudo 属于 root 并设置了 setuid 位吗

    遇见这种问题应该检查sudo文件拥有者名称 ---x--x--x. 1 cmp cmp 130720  sudo 明显拥有者有问题 chown root:root /usr/bin/sudo chmo ...