我们在学习C程序开发时经常会遇到一些概念:代码段、数据段、BSS段(Block Started by Symbol) 、堆(heap)和栈(stack)。先看一张教材上的示意图(来源,《UNIX环境高级编程》一书),显示了进程地址空间中典型的存储区域分配情况。

从图中可以看出:

  • 从低地址到高地址分别为:代码段、(初始化)数据段、(未初始化)数据段(BSS)、堆、栈、命令行参数和环境变量
  • 堆向高内存地址生长
  • 栈向低内存地址生长

还经常看到下面这个图(来源,不详):

先看一段程序。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int global_init_a=1;
  4. int global_uninit_a;
  5. static int static_global_init_a=1;
  6. static int static_global_uninit_a;
  7. const int const_global_a=1;
  8. int global_init_b=1;
  9. int global_uninit_b;
  10. static int static_global_init_b=1;
  11. static int static_global_uninit_b;
  12. const int const_global_b=1;
  13. /*上面全部为全局变量,main函数中的为局部变量*/
  14. int main()
  15. {
  16. int local_init_a=1;
  17. int local_uninit_a;
  18. static int static_local_init_a=1;
  19. static int static_local_uninit_a;
  20. const int const_local_a=1;
  21. int local_init_b=1;
  22. int local_uninit_b;
  23. static int static_local_init_b=1;
  24. static int static_local_uninit_b;
  25. const int const_local_b=1;
  26. int * malloc_p_a;
  27. malloc_p_a=malloc(sizeof(int));
  28. printf("\n         &global_init_a=%p \t
  29. global_init_a=%d\n",&global_init_a,global_init_a);
  30. printf("       &global_uninit_a=%p \t
  31. global_uninit_a=%d\n",&global_uninit_a,global_uninit_a);
  32. printf("  &static_global_init_a=%p \t
  33. static_global_init_a=%d\n",&static_global_init_a,static_global_init_a);
  34. printf("&static_global_uninit_a=%p \t
  35. static_global_uninit_a=%d\n",&static_global_uninit_a,static_global_uninit_a);
  36. printf("        &const_global_a=%p \t
  37. const_global_a=%d\n",&const_global_a,const_global_a);
  38. printf("\n         &global_init_b=%p \t
  39. global_init_b=%d\n",&global_init_b,global_init_b);
  40. printf("       &global_uninit_b=%p \t
  41. global_uninit_b=%d\n",&global_uninit_b,global_uninit_b);
  42. printf("  &static_global_init_b=%p \t
  43. static_global_init_b=%d\n",&static_global_init_b,static_global_init_b);
  44. printf("&static_global_uninit_b=%p \t
  45. static_global_uninit_b=%d\n",&static_global_uninit_b,static_global_uninit_b);
  46. printf("        &const_global_b=%p \t
  47. const_global_b=%d\n",&const_global_b,const_global_b);
  48. printf("\n          &local_init_a=%p \t
  49. local_init_a=%d\n",&local_init_a,local_init_a);
  50. printf("        &local_uninit_a=%p \t
  51. local_uninit_a=%d\n",&local_uninit_a,local_uninit_a);
  52. printf("   &static_local_init_a=%p \t
  53. static_local_init_a=%d\n",&static_local_init_a,static_local_init_a);
  54. printf(" &static_local_uninit_a=%p \t
  55. static_local_uninit_a=%d\n",&static_local_uninit_a,static_local_uninit_a);
  56. printf("         &const_local_a=%p \t
  57. const_local_a=%d\n",&const_local_a,const_local_a);
  58. printf("\n          &local_init_b=%p \t
  59. local_init_b=%d\n",&local_init_b,local_init_b);
  60. printf("        &local_uninit_b=%p \t
  61. local_uninit_b=%d\n",&local_uninit_b,local_uninit_b);
  62. printf("   &static_local_init_b=%p \t
  63. static_local_init_b=%d\n",&static_local_init_b,static_local_init_b);
  64. printf(" &static_local_uninit_b=%p \t
  65. static_local_uninit_b=%d\n",&static_local_uninit_b,static_local_uninit_b);
  66. printf("         &const_local_b=%p \t
  67. const_local_b=%d\n",&const_local_b,const_local_b);
  68. printf("             malloc_p_a=%p \t
  69. *malloc_p_a=%d\n",malloc_p_a,*malloc_p_a);
  70. return 0;
  71. }

下面是输出结果。

先仔细分析一下上面的输出结果,看看能得出什么结论。貌似很难分析出来什么结果。好了我们继续往下看吧。

接下来,通过查看proc文件系统下的文件,看一下这个进程的真实内存分配情况。(我们需要在程序结束前加一个死循环,不让进程结束,以便我们进一步分析)。

在return 0前,增加 while(1); 语句

重新编译后,运行程序,程序将进入死循环。

使用ps命令查看一下进程的pid

#ps -aux | grep a.out

查看/proc/2699/maps文件,这个文件显示了进程在内存空间中各个区域的分配情况。

#cat  /proc/2699/maps

上面红颜色标出的几个区间是我们感兴趣的区间:

  • 08048000-08049000  r-xp  貌似是代码段
  • 08049000-0804a000 r--p   暂时不清楚,看不出来
  • 0804a000-0804b000 rw-p  貌似为数据段
  • 08a7e000-08a9f000  rw-p  堆
  • bff73000-bff88000     rw-p   栈

我们把这些数据与最后一次的程序运行结果进行比较,看看有什么结论。

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
            &global_uninit_a=0x804a04c      全局未初始化:数据段          global_uninit_a=0
     &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
&static_global_uninit_a=0x804a038      全局静态未初始化:数据段     static_global_uninit_a=0
             &const_global_a=0x80487c0     全局只读变量: 代码段        const_global_a=1

&global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
            &global_uninit_b=0x804a048        全局未初始化:数据段      global_uninit_b=0
     &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
&static_global_uninit_b=0x804a03c        全局静态未初始化:数据段   static_global_uninit_b=0
            &const_global_b=0x80487c4        全局只读变量: 代码段             const_global_b=1

&local_init_a=0xbff8600c          局部初始化:栈                     local_init_a=1
             &local_uninit_a=0xbff86008         局部未初始化:栈                 local_uninit_a=134514459
     &static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
 &static_local_uninit_a=0x804a040        局部静态未初始化:数据段     static_local_uninit_a=0
             &const_local_a=0xbff86004        局部只读变量:栈     const_local_a=1

&local_init_b=0xbff86000        局部初始化:栈          local_init_b=1
                &local_uninit_b=0xbff85ffc         局部未初始化:栈        local_uninit_b=-1074241512
      &static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1
 &static_local_uninit_b=0x804a044        局部静态未初始化:数据段      static_local_uninit_b=0
                &const_local_b=0xbff85ff8        局部只读变量:栈        const_local_b=1

p_chars=0x80487c8        字符串常量:代码段          p_chars=abcdef
                    malloc_p_a=0x8a7e008        malloc动态分配:堆        *malloc_p_a=0

通过以上分析我们暂时可以得到的结论如下,在进程的地址空间中

  • 数据段中存放:全局变量(初始化以及未初始化的)、静态变量(全局的和局部的、初始化的以及未初始化的)
  • 代码段中存放:全局只读变量(const)、字符串常量
  • 堆中存放:动态分配的区域
  • 栈中存放:局部变量(初始化以及未初始化的,但不包含静态变量)、局部只读变量(const)

这里我们没有发现BSS段,但是我们将未初始化的数据按照地址进行排序看一下,可以发现一个规律。

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
    &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
                &global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
    &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
       &static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
       &static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1

&static_global_uninit_a=0x804a038      全局静态未初始化:数据段     static_global_uninit_a=0
&static_global_uninit_b=0x804a03c        全局静态未初始化:数据段   static_global_uninit_b=0
  &static_local_uninit_a=0x804a040        局部静态未初始化:数据段     static_local_uninit_a=0
  &static_local_uninit_b=0x804a044        局部静态未初始化:数据段      static_local_uninit_b=0
           &global_uninit_b=0x804a048        全局未初始化:数据段      global_uninit_b=0
            &global_uninit_a=0x804a04c      全局未初始化:数据段          global_uninit_a=0

这里可以发现,初始化的和未初始化的数据好像是分开存放的,因此我们可以猜测BSS段是存在的,只不过数据段是分为初始化和未初始化(即BSS段)的两部分,他们在加载到进程地址空间时是合并为数据段了,在进程地址空间中没有单独分为一个区域。

还有一个问题,静态数据与非静态数据是否是分开存放的呢?请读者自行分析一下。

接下来我们从程序的角度看一下,这些存储区域是如何分配的。首先我们先介绍一下ELF文件格式。

ELF(Executable and Linkable Format )文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:
–可重定位的目标文件(Relocatable,或者Object File)
–可执行文件(Executable)
–共享库(Shared Object,或者Shared Library)
 
下图为ELF文件的结构示意图(来源,不详):

一个程序编译生成目标代码文件(ELF文件)的过程如下,此图引自《程序员的自我修养》一书的一个图:

可以通过readelf命令查看EFL文件的相关信息,例如 readelf  -a  a.out  ,我们只关心各个段的分配情况,因此我们使用以下命令:

    # readelf -S a.out
                        

将这里的内存布局与之前看到的程序的运行结果进行分析:

&global_init_a=0x804a018       全局初始化:数据段              global_init_a=1
            &global_uninit_a=0x804a04c      全局未初始化:BSS段          global_uninit_a=0
     &static_global_init_a=0x804a01c      全局静态初始化:数据段      static_global_init_a=1
&static_global_uninit_a=0x804a038      全局静态未初始化:BSS段     static_global_uninit_a=0
             &const_global_a=0x80487c0     全局只读变量: 只读数据段        const_global_a=1

&global_init_b=0x804a020       全局初始化:数据段      global_init_b=1
            &global_uninit_b=0x804a048        全局未初始化:BSS段      global_uninit_b=0
     &static_global_init_b=0x804a024        全局静态初始化:数据段    static_global_init_b=1
&static_global_uninit_b=0x804a03c        全局静态未初始化:BSS段   static_global_uninit_b=0
            &const_global_b=0x80487c4        全局只读变量: 只读数据段             const_global_b=1

&static_local_init_a=0x804a028         局部静态初始化:数据段      static_local_init_a=1
 &static_local_uninit_a=0x804a040        局部静态未初始化:BSS段     static_local_uninit_a=0

&static_local_init_b=0x804a02c        局部静态初始化:数据段      static_local_init_b=1
 &static_local_uninit_b=0x804a044        局部静态未初始化:BSS段      static_local_uninit_b=0

p_chars=0x80487c8        字符串常量:只读数据段          p_chars=abcdef
ELF 文件一般包含以下几个段 :

  • .text section:主要是编译后的源码指令,是只读字段。
  • .data section :初始化后的非const的全局变量、局部static变量。
  • .bss:未初始化后的非const全局变量、局部static变量。
  • .rodata字段  是存放只读数据 

分析到这以后,我们在和之前分析的结果对比一下,会发现确实存在BSS段,地址为0804a030 ,大小为0x20,之前我们的程序中未初始化的的确存放在这个地址区间中了,只不过执行exec系统调用时,将这部分的数据初始化为0后,放到了进程地址空间的数据段中了,在进程地址空间中就没有必要存在BSS段了,因此都称做数据段。同理,.rodata字段也是与text段放在一起了。

在ELF文件中,找不到局部非静态变量和动态分配的内容。

以上有很多地方的分析,我在网上基本找不到很明确的结论,很多教材上也没有描述,只是我通过程序分析得出的结论,如有不妥之处,请指出,欢迎交流。

作者:沧海猎人   出处:http://blog.csdn.net/embedded_hunter  转载请注明出处   嵌入式技术交流QQ群:179012822

Linux下C程序进程地址空间布局[转]的更多相关文章

  1. Linux下C程序的存储空间布局

    一个程序本质上都是由 BSS 段.data段.text段三个组成的.可以看到一个可执行程序在存储(没有调入内存)时分为代码段.数据区和未初始化数据区三部分. BSS段(未初始化数据区):在采用段式内存 ...

  2. Linux下查看某个进程打开的文件数-losf工具常用参数介绍

    Linux下查看某个进程打开的文件数-losf工具常用参数介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在linux操作系统中,一切皆文件.通过文件不仅仅可以访问常规数据,还 ...

  3. Linux下逻辑地址、线性地址、物理地址详细总结

    Linux下逻辑地址.线性地址.物理地址详细总结 一.逻辑地址转线性地址      机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址 ...

  4. linux下实现监控进程网络带宽

    嗯,近期都在网易游戏实习,所以貌似有段时间没有上来写点东西了... 来网易游戏实习最基本的目的事实上就是想知道在游戏公司里面工作都是些什么内容,毕竟自己曾经也没有接触过游戏公司.. 还比較的好奇.. ...

  5. [转帖]Linux下逻辑地址、线性地址、物理地址详细总结

    Linux下逻辑地址.线性地址.物理地址详细总结 https://www.cnblogs.com/alantu2018/p/9002441.html 总结的挺好的 现在应该是段页式管理 使用MMU和T ...

  6. Linux下C程序的内存映像

    2.Linux下C程序的内存映像 2.1. 代码段.只读数据段(1)对应着程序中的代码(函数),代码段在Linux中又叫文本段(.text)(2)只读数据段就是在程序运行期间只能读不能写的数据,con ...

  7. Linux下C程序的编辑,编译和运行以及调试

    国内私募机构九鼎控股打造APP,来就送 20元现金领取地址:http://jdb.jiudingcapital.com/phone.html 内部邀请码:C8E245J (不写邀请码,没有现金送) 国 ...

  8. 解决linux下tomcat停止进程任存在问题

    解决linux下tomcat停止进程任存在问题 在Linux下(之所以强调linux下,是因为在windows下正常),执行tomcat ./shutdown.sh 后,虽然tomcat服务不能正常访 ...

  9. Linux下C程序内存泄露检测

    在linux下些C语言程序,最大的问题就是没有一个好的编程IDE,当然想kdevelop等工具都相当的强大,但我还是习惯使用kdevelop工具,由于没有一个习惯的编程IDE,内存检测也就成了在lin ...

随机推荐

  1. java——int、args[]传参、标签、数字塔?、一个输入格式

    1.当int型整数超出自己范围时,会从它的上界重新开始. public class exp { public static void main(String[] args) { int i = 214 ...

  2. Nuxt 2.3.X 配置sass

    1.需要安装node-sass和sass-loader就行了 npm i -S node-sass sass-loader

  3. DataFrame的数据类型转换

    dfxxx['username']=pd.to_numeric(dfxxx['username'],errors='coerce')#将不能转换数据类型的值强制转换成NaN dfxxx['userna ...

  4. Broken Keyboard (a.k.a. Beiju Text) UVA - 11988 (链表)

    题目链接:https://vjudge.net/problem/UVA-11988 题目大意:输入一个字符串,输出在原本应该是怎么样的?  具体方法是 碰到' [ ' 回到最前面  碰到‘ ]’  回 ...

  5. python-常用模块整理

    学习背景 最近需要进行文件夹数据的批量读取,里面会用到python里面的os模块.但是对os模块又不是很熟悉,里面的函数有哪些函数?有什么用?怎么用?英语不好的每次看官方文档也比较费力,所以就想着看看 ...

  6. inventor怎样卸载干净

    AUTODESK系列软件着实令人头疼,安装失败之后不能完全卸载!!!(比如maya,cad,3dsmax等).有时手动删除注册表重装之后还是会出现各种问题,每个版本的C++Runtime和.NET f ...

  7. [转]AngularJS+UI Router(1) 多步表单

    本文转自:https://www.zybuluo.com/dreamapplehappy/note/54448 多步表单的实现   在线demo演示地址https://rawgit.com/dream ...

  8. Murano Setup Steps

    1. Select a Linux Distribution Only Ubuntu 14.04 (Trusty), Fedora 21 (or Fedora 22) and CentOS/RHEL ...

  9. VB.NET中的模块

    在C#中有“静态类”的概念,自然里边全部的方法都是静态的.这意味着你可以直接通过"类名.方法名"去调用(例如System的Math类就是典型).在VB.NET中,没有“静态类”的概 ...

  10. pat1025. PAT Ranking (25)

    1025. PAT Ranking (25) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Programmi ...