文档章节

Linux下C程序进程地址空间布局

zhangyujsj
 zhangyujsj
发布于 2014/04/11 10:31
字数 2288
阅读 112
收藏 10

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

           

从图中可以看出:

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

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

                                                                   

 

先看一段程序。

 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. int global_init_a=1;  
  5. int global_uninit_a;  
  6. static int static_global_init_a=1;  
  7. static int static_global_uninit_a;  
  8. const int const_global_a=1;  
  9.   
  10. int global_init_b=1;  
  11. int global_uninit_b;  
  12. static int static_global_init_b=1;  
  13. static int static_global_uninit_b;  
  14. const int const_global_b=1;  
  15. /*上面全部为全局变量,main函数中的为局部变量*/  
  16. int main()  
  17. {  
  18.     int local_init_a=1;  
  19.     int local_uninit_a;  
  20.     static int static_local_init_a=1;  
  21.     static int static_local_uninit_a;  
  22.     const int const_local_a=1;  
  23.   
  24.     int local_init_b=1;  
  25.     int local_uninit_b;  
  26.     static int static_local_init_b=1;  
  27.     static int static_local_uninit_b;  
  28.     const int const_local_b=1;  
  29.   
  30.     int * malloc_p_a;  
  31.     malloc_p_a=malloc(sizeof(int));  
  32.   
  33.     printf("\n         &global_init_a=%p \t            
  34.          global_init_a=%d\n",&global_init_a,global_init_a);   
  35.   
  36.     printf("       &global_uninit_a=%p \t          
  37.         global_uninit_a=%d\n",&global_uninit_a,global_uninit_a);      
  38.   
  39.     printf("  &static_global_init_a=%p \t     
  40.         static_global_init_a=%d\n",&static_global_init_a,static_global_init_a);  
  41.       
  42.     printf("&static_global_uninit_a=%p \t   
  43.         static_global_uninit_a=%d\n",&static_global_uninit_a,static_global_uninit_a);  
  44.       
  45.     printf("        &const_global_a=%p \t           
  46.         const_global_a=%d\n",&const_global_a,const_global_a);     
  47.   
  48.       
  49.     printf("\n         &global_init_b=%p \t            
  50.         global_init_b=%d\n",&global_init_b,global_init_b);    
  51.   
  52.     printf("       &global_uninit_b=%p \t          
  53.         global_uninit_b=%d\n",&global_uninit_b,global_uninit_b);      
  54.   
  55.     printf("  &static_global_init_b=%p \t     
  56.         static_global_init_b=%d\n",&static_global_init_b,static_global_init_b);  
  57.       
  58.     printf("&static_global_uninit_b=%p \t   
  59.         static_global_uninit_b=%d\n",&static_global_uninit_b,static_global_uninit_b);  
  60.       
  61.     printf("        &const_global_b=%p \t           
  62.         const_global_b=%d\n",&const_global_b,const_global_b);  
  63.   
  64.                   
  65.   
  66.     printf("\n          &local_init_a=%p \t            
  67.         local_init_a=%d\n",&local_init_a,local_init_a);   
  68.   
  69.     printf("        &local_uninit_a=%p \t          
  70.         local_uninit_a=%d\n",&local_uninit_a,local_uninit_a);  
  71.       
  72.     printf("   &static_local_init_a=%p \t     
  73.         static_local_init_a=%d\n",&static_local_init_a,static_local_init_a);  
  74.       
  75.     printf(" &static_local_uninit_a=%p \t   
  76.         static_local_uninit_a=%d\n",&static_local_uninit_a,static_local_uninit_a);    
  77.   
  78.     printf("         &const_local_a=%p \t           
  79.         const_local_a=%d\n",&const_local_a,const_local_a);    
  80.   
  81.       
  82.     printf("\n          &local_init_b=%p \t            
  83.         local_init_b=%d\n",&local_init_b,local_init_b);   
  84.   
  85.     printf("        &local_uninit_b=%p \t          
  86.         local_uninit_b=%d\n",&local_uninit_b,local_uninit_b);  
  87.       
  88.     printf("   &static_local_init_b=%p \t     
  89.         static_local_init_b=%d\n",&static_local_init_b,static_local_init_b);  
  90.       
  91.     printf(" &static_local_uninit_b=%p \t   
  92.         static_local_uninit_b=%d\n",&static_local_uninit_b,static_local_uninit_b);    
  93.   
  94.     printf("         &const_local_b=%p \t           
  95.         const_local_b=%d\n",&const_local_b,const_local_b);  
  96.   
  97.   
  98.     printf("             malloc_p_a=%p \t             
  99.         *malloc_p_a=%d\n",malloc_p_a,*malloc_p_a);  
  100.       
  101.     return 0;  
  102. }  

 

下面是输出结果。

           

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

 

接下来,通过查看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

本文转载自:http://blog.csdn.net/embedded_hunter/article/details/6897027

共有 人打赏支持
zhangyujsj
粉丝 23
博文 358
码字总数 224241
作品 0
广州
私信 提问
linux 进程地址空间的一步步探究

我们知道,在32位机器上linux操作系统中的进程的地址空间大小是4G,其中0-3G是用户空间,3G-4G是内核空间。其实,这个4G的地址空间是不存在的,也就是我们所说的虚拟内存空间。 那虚拟内存空间...

HelloRookie
06/21
0
0
c执行文件内存布局

c语言在嵌入式、操作系统、图像处理方面应用广泛,是一种比较底层的语言。本文主要介绍c语言的内存分配,进程在内存中的布局。 环境: Linux zhuzhu 4.2.0-27-generic #32~14.04.1-Ubuntu S...

菏泽小朱
2016/10/07
0
0
Linux 进程栈和线程栈的区别

注:本文所涉及的环境为Linux, 下文讨论的栈跟内核栈,没有任何的关系,关于内核栈,请参考《深入Linux内核架构》中的2.4.1 进程复制 这里有如下几个问题,线程栈的空间是开辟在那里的? 线...

地狱的烈火
2013/05/25
0
0
OS内存管理相关实现(原题: Linux中的内存管理)

前一段时间看了《深入理解Linux内核》对其中的内存管理部分花了不少时间,但是还是有很多问题不是很清楚,最近又花了一些时间复习了一下,在这里记录下自己的理解和对Linux中内存管理的一些看...

消失了雨痕
2013/06/21
0
2
内存管理之程序内存分布

在多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盘中。这个沙盘就是虚拟地址空间(virtual address space)。 1 32位虚拟内存布局 在32位模式下虚拟地址空间总是一个4GB的内存...

wangdy
2016/06/14
49
0

没有更多内容

加载失败,请刷新页面

加载更多

腾讯与Github的魔幻会面背后的故事…

10月22日,腾讯开源管理办公室有幸邀请到Github新晋CEO Nat Friedman,前来鹅厂参观交流。目前腾讯已经有近70个项目在Github上开源,共获得17w stars,世界排名11位。Github是腾讯开源的主阵...

腾讯开源
22分钟前
1
0
单例模式

单例模式(Singleton pattern)属于创建型设计模式。 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对...

NinjaFrog
27分钟前
0
0
TypeScript基础入门之装饰器(三)

转载 TypeScript基础入门之装饰器(三) 继续上篇文章[TypeScript基础入门之装饰器(二)] 访问器装饰器 Accessor Decorator在访问器声明之前声明。 访问器装饰器应用于访问器的属性描述符,可用...

durban
44分钟前
2
0
spring5调研学习(转载)

Spring框架的新功能 这一章主要提供Spring框架新的功能和变更。 升级到新版本的框架可以参考。Spring git。 内容列表 Spring 5.x框架新的功能 Spring 4.x框架新的功能 Spring 3.x框架新的功能...

小海bug
54分钟前
2
0
为何Spring框架能这么流行?

想要学习更多关于Spring框架在Java开发者中如此流行?看这篇文章可以学到更多! Spring框架特性 Spring是用于应用开发中的一款强大,轻量级框架。更广泛地说,你可以认为Spring框架是一个定义...

java知识分子
57分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部