Linux下c程序地址空間佈局

我們在學習C程序開發時經常會遇到一些概念:代碼段、數據段、BSS段(Block Started by Symbol) 、堆(heap)和棧(stack)。先看一張教材上的示意圖(來源,《UNIX環境高級編程》一書),顯示了進程地址空間中典型的存儲區域分配情況。

           

從圖中可以看出:

  • 從低地址到高地址分別爲:代碼段、(初始化)數據段、(未初始化)數據段(BSS)、堆、棧、命令行參數和環境變量
  • 堆向高內存地址生長
  • 棧向低內存地址生長

還經常看到下面這個圖(來源,不詳):

                                                                   

 

先看一段程序。

 

  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/article/details/6897027 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章