程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 一起talk C栗子吧(第一百三十回:C語言實例--C程序內存布局二)

一起talk C栗子吧(第一百三十回:C語言實例--C程序內存布局二)

編輯:關於C語言

一起talk C栗子吧(第一百三十回:C語言實例--C程序內存布局二)



各位看官們,大家好,上一回中咱們說的是C程序內存布局的例子,這一回咱們繼續說該例子。閒話休提,言歸正轉。讓我們一起talk C栗子吧!

看官們,我們在上一回中介紹了C程序在內存中的布局,並且給大家做了簡單的演示。上一回的例子比較簡單,只能說明程序中內存布局的大體輪廓。我們今天會通過具體的內存地址來清楚地介紹C程序在內存中的布局。下面是例子的程序源代碼,請大家參考:

int ga1;
int ga2 = 1;

int func()
{
    int i = 0;
    static static_la1;
    static static_la2 = 3;

    printf("func is running \n");

    printf("Address of i: %p \n",&i);
    printf("Address of static_la1 : %p \n",&static_la1);
    printf("Address of static_la2 : %p \n",&static_la2);

    while(i++<8)
        sleep(1);

    return 0;
}

int main()
{
    int la1;
    int la2 = 2;
    int *p;

    p = (int *) malloc(3*sizeof(int));

    if(NULL == p)
        printf("malloc failed \n");

    printf("Address of ga1: %p \n",&ga1);
    printf("Address of ga2: %p \n",&ga2);
    printf("Address of la1: %p \n",&la1);
    printf("Address of la2: %p \n",&la2);
    printf("Address of p: %p \n",p);

    func();

    free(p);
    p = NULL;

    return 0;
}

在該代碼中,我們手動輸出各個變量的地址,這樣做的目的是為了通過變量的地址來判斷變量在內存中的位置,進而確認變量屬於內存布局中的哪個分區。那麼這些變量究竟是在哪個分區中呢?data分區還是bss分區?下面是程序的運行結果,請大家參考:

Address of ga1: 0x804a040 
Address of ga2: 0x804a030 
Address of la1: 0xbfc7e7b4 
Address of la2: 0xbfc7e7b8 
Address of p: 0x8ffc008 
func is running 
Address of i: 0xbfc7e78c 
Address of static_la1 : 0x804a03c 
Address of static_la2 : 0x804a034 

從程序的運行結果中我們可以看到,各個變量在內存中的地址,不過,我們還是不知道這些變量屬於哪個內存分區。看官們莫急,我們通過readelf工具來查看該程序的內存布局,通過內存布局就可以看到各個內存分區的地址范圍,這些我就們就能依據變量的地址推斷出變量所在的內存分區。下面是詳細的結果:

readelf -S s            //使用readelf 工具查看程序的內存布局
There are 30 section headers, starting at offset 0x1190:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000090 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804825c 00025c 000063 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          080482c0 0002c0 000012 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         080482d4 0002d4 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482f4 0002f4 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482fc 0002fc 000038 08   A  5  12  4
  [11] .init             PROGBITS        08048334 000334 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        08048360 000360 000080 04  AX  0   0 16
  [13] .text             PROGBITS        080483e0 0003e0 0002a2 00  AX  0   0 16
  [14] .fini             PROGBITS        08048684 000684 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048698 000698 0000dc 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        08048774 000774 000034 00   A  0   0  4
  [17] .eh_frame         PROGBITS        080487a8 0007a8 0000d0 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000028 04  WA  0   0  4
  [24] .data             PROGBITS        0804a028 001028 000010 00  WA  0   0  4
  [25] .bss              NOBITS          0804a038 001038 00000c 00  WA  0   0  4
  [26] .comment          PROGBITS        00000000 001038 00004f 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 001087 000106 00      0   0  1
  [28] .symtab           SYMTAB          00000000 001640 0004c0 10     29  47  4
  [29] .strtab           STRTAB          00000000 001b00 0002be 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

從運行結果中可以看到程序的

data分區從地址0x0804a028開始,大小為0x000010(16byte)。
bss分區從0x0804a038開始,大小為0x00000c(12byte)。

那麼我們結合程序中變量的地址來推斷一下變量所在的內存分區:

Address of ga1: 0x804a040    //變量的地址大於bss(0x0804a038),變量位於bss分區
Address of ga2: 0x804a030    //變量的地址大於data(0x0804a028),而且小於bss,變量屬於data分區
Address of static_la1 : 0x804a03c    //變量的地址大於bss(0x0804a038),變量位於bss分區
Address of static_la2 : 0x804a034    //變量的地址大於data(0x0804a028),而且小於bss,變量屬於data分區

還有四個變量的地址不在bss和data分區范圍內,因此不能確定這些變量所在的分區:

Address of la1: 0xbfc7e7b4   //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of la2: 0xbfc7e7b8   //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of p: 0x8ffc008      //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍
Address of i: 0xbfc7e78c     //變量的地址大於bss(0x0804a038),但是超出了bss分區的范圍

我們在前一章回中介紹過,重要的分區就四種,他們不在data和bss,那麼只能在堆區或者棧區了。現在我們只有變量的地址,要是有堆區或者棧區的地址范圍就好了。這樣我們就可以向剛才推斷bss和data分區中的變量一樣來推斷這些變量在內存中的分區。

接下我們一起查找一下程序的棧和堆分區。還記得我們在前面章回中介紹的proc虛擬文件系統嗎?我們可以借助它來獲得棧區和堆區的內存空間信息。

 ./s &                               //在後台運行編譯好的程序
[1] 2736                                                                                    //程序在後台正常運行,同時顯示程序的PID
Address of ga1: 0x804a040            //程序運行,輸出程序中的內容
Address of ga2: 0x804a030 
Address of la1: 0xbfe01cf4 
Address of la2: 0xbfe01cf8 
Address of p: 0x964d008 
func is running 
Address of i: 0xbfe01ccc 
Address of static_la1 : 0x804a03c 
Address of static_la2 : 0x804a034 
cat /proc/2736/maps                   //查看proc中關於進程的信息
08048000-08049000 r-xp 00000000 08:13 22415696   /home/talk8/s
08049000-0804a000 r--p 00000000 08:13 22415696   /home/talk8/s
0804a000-0804b000 rw-p 00001000 08:13 22415696   /home/talk8/s
0964d000-0966e000 rw-p 00000000 00:00 0          [heap]  
b75b7000-b75b8000 rw-p 00000000 00:00 0 
b75b8000-b7761000 r-xp 00000000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7761000-b7763000 r--p 001a9000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7763000-b7764000 rw-p 001ab000 08:16 6444       /lib/i386-linux-gnu/libc-2.19.so
b7764000-b7767000 rw-p 00000000 00:00 0 
b777e000-b7781000 rw-p 00000000 00:00 0 
b7781000-b7782000 r-xp 00000000 00:00 0          [vdso]
b7782000-b77a2000 r-xp 00000000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
b77a2000-b77a3000 r--p 0001f000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
b77a3000-b77a4000 rw-p 00020000 08:16 6459       /lib/i386-linux-gnu/ld-2.19.so
bfde3000-bfe04000 rw-p 00000000 00:00 0          [stack]

從上面信息中我們可以看到,標記為[heap]和[stack]的分區就是我們要找的堆區和棧區,與其在同一行中的內容顯示了這兩個分區的內存地址空間。

堆區:0x0964d000-0x0966e000
棧區:0xbfde3000-0xbfe04000

現在,我們可以推斷剛才哪四個不知道自己所在分區的變量了,下面是我們的推斷結果:

Address of la1: 0xbfe01cf4   //變量的地址位於棧區地址空間內,該變量位於棧區
Address of la2: 0xbfe01cf8   //變量的地址位於棧區地址空間內,該變量位於棧區
Address of p: 0x964d008      //變量的地址位於堆區地址空間內,該變量位於堆區
Address of i: 0xbfe01ccc     //變量的地址位於棧區地址空間內,該變量位於棧區

看官們,到目前為止, 我們通過具體的內存地址清楚地展示了C程序在內存中的布局。最後我們畫一個直觀的圖形給大家看,這樣大家就能直觀地看清楚C程序的內存模型了。
這裡寫圖片描述下載使用(CSDN又出問題了,所以暫時不能上傳程序,等CSDN修復好問題後,我會及時上傳程序,到時候大家可以到我們資源中下載程序)。因為每個人的電腦是不同的,而且所使用的編程環境也可能不相同,所以建議各位看官都下載程序到自己的電腦上運行,然後按照我們介紹的方法觀察程序在內存中的模型。

各位看官,關於C程序內存布局的例子咱們就說到這裡。欲知後面還有什麼例子,且聽下回分解 。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved