为什么须要知道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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图2
进程内存布局

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

数据存储类别

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

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

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

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

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

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

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

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

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

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

2、静态对象(static
objects)

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

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

一个实例

以下我们分析一段代码:

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

程序中声明的变量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之间

用图表演示样例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

图3
样例的内存布局

总结

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

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

内存能够分为下面几段:

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

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

  • 栈:由系统管理,由高地址向低地址扩展。
  • 堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间。通过free、delete/delete[]释放所申请的
  • 空间。由低地址想高地址扩展



C++编译过程与内存空间的更多相关文章

  1. 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 百篇博客分析OpenHarmony源码| v57.01

    百篇博客系列篇.本篇为: v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视编译全过程 | 51.c.h.o 编译构建相关篇为: v50.xx 鸿蒙内核源码分析(编译环境篇) | 编译鸿蒙 ...

  2. Java编译过程、c/c++编译过程区别

    Java编译原理 1.Java编译过程与c/c++编译过程不同 Java编译程序将java源程序编译成jvm可执行代码--java字节码. c/c++编译过程: 当C编译器编译生成一个对象的代码时,该 ...

  3. Linux内存点滴:用户进程内存空间

    原文出处:PerfGeeks 经常使用top命令了解进程信息,其中包括内存方面的信息.命令top帮助文档是这么解释各个字段的.VIRT , Virtual Image (kb)RES, Residen ...

  4. Linux内存点滴 用户进程内存空间

    Linux内存点滴 用户进程内存空间 经常使用top命令了解进程信息,其中包括内存方面的信息.命令top帮助文档是这么解释各个字段的. VIRT, Virtual Image (kb) RES, Re ...

  5. [置顶] 浅谈Android的资源编译过程

    Android APK 一.APK的结构以及生成 APK是Android Package的缩写,即Android application package文件或Android安装包.每个要安装到Andr ...

  6. 【转】 Apk文件及其编译过程

    Apk文件概述 Android系统中的应用程序安装包都是以apk为后缀名,其实apk是Android Package的缩写,即android安装包. 注:apk包文件其实就是标准的zip文件,可以直接 ...

  7. java中的内存空间 堆和栈

        认识堆与栈 栈与堆都是Java用来在Ram中存放数据的地方.与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆.Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过 ...

  8. GCC编译过程与动态链接库和静态链接库

    1. 库的介绍 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常. 本质上来说库是一种可执行代码的二进制形式,可 ...

  9. MDK 的编译过程及文件类型全解

    MDK 的编译过程及文件类型全解 ------(在arm9的开发中,这些东西都是我们自己搞定的,但是在windows上,IDE帮我们做好了,了解这些对深入开发是很有帮助的,在有arm9开发的基础上,下 ...

随机推荐

  1. 深入理解JavaScript中的函数操作——《JavaScript忍者秘籍》总结

    匿名函数 对于什么是匿名函数,这里就不做过多介绍了.我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性.通常,匿名函数的使用情况是:创建一个供以后使用的函数.简单的 ...

  2. https://v2ex.com/t/170386

    https://v2ex.com/t/170386 https://cnodejs.org/topic/5566952ad4ca459f5267ac59 https://segmentfault.co ...

  3. gcc编译错误:DSO missing from command line

    在用gcc 编译连接的时候,可能会遇到类似以下的错误: /usr/bin/ld: test_desktop_utils-test-desktop-utils.o: undefined referenc ...

  4. Python 自用代码(某方标准类网页源代码清洗)

    用于mongodb中“标准”数据的清洗,数据为网页源代码,须从中提取: 标准名称,标准外文名称,标准编号,发布单位,发布日期,状态,实施日期,开本页数,采用关系,中图分类号,中国标准分类号,国际标准分 ...

  5. shell报错:未预期的符号***附近有语法错误

    1.安装dos2unix(我的centos命令: yum install dos2unix)2.执行命令:dos2unix  文件名.sh 3.执行命令:bash  -n  文件名.sh (检查语法错 ...

  6. php核心技术与最佳实践知识点(上)

    一.基础 1.serialize:序列化一个类,只是保存了类的属性,所以还需要反序列化unserialize的时候包含该类. 2.对于将array转为object,这个转换因为没有具体的类,所以称为了 ...

  7. 协程基础_context系列函数

    近期想看看协程,对这个的详细实现不太了解.查了下,协程最常规的做法就是基于makecontext,getcontext,swapcontext这类函数在用户空间切换用户上下文. 所以在这通过样例代码尽 ...

  8. 实用且免费API接口2

    之前已经整理过一些免费API,现在在知乎专栏上看到别人整理的一些实用免费API,有一些是没有重复的,因此也搬过来. 今天的内容,很适合你去做一些好玩.实用的东西出来. 先来科普个概念,开放应用程序的A ...

  9. C++中的INL(转)

    inl 文件是内联函数的源文件. 内联函数通常在c++头文件中实现,但有的时候内联函数较多或者出于一些别的考虑(使头文件看起来更简洁等), 往往会将这部分具体定义的代码添加到INL文件中,然后在该头文 ...

  10. ActiveMQ简述

    概述 ActiveMQ是Apache所提供的一个开源的消息系统,全然採用Java来实现.因此.它能非常好地支持J2EE提出的JMS(Java Message Service,即Java消息服务)规范. ...