【C/C++】C語言內存模型 (C memory layout)

 一. 內存模型                                                                                                                                    html

 

1. .text數組

    代碼區(code section)。由編譯器連接器生成的可執行指令,程序執行時由加載器(loader)從可執行文件拷貝到內存中。爲了安全考慮,防止別的區域更改代碼區數據(便可執行指令),代碼區具備只讀屬性。另外一個方面,代碼區一般具備可共享性(sharable),即在內存中只有一份代碼區,如編譯器,假如同時有多個編譯任務在執行,這些編譯任務會共享編譯器的代碼區,但同時各個編譯任務又有本身獨立的區域。安全

2. .rodata函數

    只讀數據區(read-only section)。包含:只讀全局變量,只讀字符串變量,只讀靜態(static)變量。程序執行時由加載器(loader)從可執行文件拷貝到內存中。工具

3. .dataspa

    可寫數據區(RW section)。包括:可寫全局變量,可寫靜態(static)變量。程序執行時由加載器(loader)從可執行文件拷貝到內存中。.net

4. . bssdebug

    未初始化數據區(un-initialized section)。包括:未初始化或初始化爲零的全局變量,未初始化或初始化爲零的靜態(static)變量。爲了減少可執行文件的大小,在可執行文件中bss區只是一個佔位符。在程序執行時,加載器(loader)根據bss區的大小,在內存中開闢相應空間,同時將這些內存空間所有初始化爲零。調試

    .text, .rodata, .data, .bss四個區域,統稱爲編譯時內存(compiler-time memory),顧名思義,這些區域的大小在編譯時就能夠決定。code

5. heap

    堆區。對於C語言而言,heap指程序運行時(run-time)由malloc, calloc, realloc等函數分配的內存。

6. stack

    棧區。每一次函數調用,都會發生一次壓棧操做,被壓棧數據稱爲一個棧幀(stack frame),有多少次函數調用(包括main()函數),棧區就有多少個棧幀。相應的,每一次函數調用返回,都會相應的發生一次出棧操做,棧幀就會減小一個。

    函數調用時,根據壓棧的順序,依次須要壓棧的數據包括:調用函數(caller funtion)的上下文環境(context environment),如寄存器;函數返回地址;被調用函數(called funtion)的參數列表;被調用函數的非靜態(static)局部變量。

    當棧區溢出(stack overflow/underflow)時,棧區數據被污染,程序執行錯誤,甚至「跑飛"(函數返回地址被修改)。

7. 示例代碼

1 /* empty-main.c */
2 #include <stdio.h>
3  
4 int main(void)
5 {
6     return 0;
7 }
 1 /* hello-mac.c */
 2 
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 
 6 int g_init_2[2] = {1, 2};            /* .data */
 7 const int gc_int_3[3] = {1, 2, 3};   /* .rodata */
 8 int g_initWithZero_4[4] = {0};       /* .bss */
 9 int g_unInit_5[5];                   /* .bss */
10 
11 extern int mac(int a, int b, int c);
12 
13 int main(void)
14 {
15     static int s_init_6[6] = {1, 2, 3, 4, 5, 6};            /* .data */
16     static const int sc_int_7[7] = {1, 2, 3, 4, 5 ,6, 7};   /* .rodata */
17     static int s_initWithZero_8[8] = {0};                   /* .bss */
18     static int s_unInit_9[9];                               /* .bss */
19 
20     int mac_out;                            /* stack */
21     int *heap_10 = (int*)malloc(10 * sizeof(int)); /* heap */
22 
23     mac_out = mac(1, 2, 3);
24     printf("mac=%d\n", mac_out);    /* .rodata string */
25 
26     free(heap_10);
27     return 0;
28 }
1 /* mac.c */
2 
3 #include <stdio.h>
4 
5 int mac(int a, int b, int c)
6 {
7     return a + b * c;??
8 }

二. 如何得到compiler-time memory consumption:.text, .rodata, .data, .bss                                     

首先,必須說明的是,下面提到的三種方法,題主也有不少沒有弄明白的地方,尤爲是對於對齊的考慮。不過使用objdump -x的方法,能夠很清楚的驗證,上面示例代碼中對變量屬於哪一個區的描述都是正確的。

1. 藉助size/objdump等工具

    首先,咱們使用gcc在Linux平臺編譯連接上面的hello-mac.c和mac.c兩個源文件:

$ gcc -o hello-mac hello-mac.c mac.c
$ gcc -o empty-main empty-main.c

而後咱們可使用size或者objdump工具來比較兩個可執行文件的區別。

1.1 咱們嘗試用size命令看看:

$ size hello-mac empty-main
   text    data     bss     dec     hex filename
   1540     616     184    2340     924 hello-mac
   1115     552       8    1675     68b empty-main

其中text表示只讀區(.text和.rodata),data爲.data初始化的全局變量或靜態變量,bss表示未初始化全局變量或靜態變量。dec爲前三者的和,hex爲dec列的16進製表示。

    這裏之因此使用empty-main,是爲了剔除掉glibc等系統佔用的內存。

1.2 咱們嘗試用objdump -x命令:

$ objdump -x hello-mac
$ objdump -x empty-main

在輸出中,咱們能查看到更加詳細的信息,比size的信息要多得多。包括咱們前面定義的全局變量和靜態變量分別屬於.rodata, .data和.bss,均有清晰的交待。

    在輸出的map文件中,咱們能夠看到main和mac兩個函數的.text大小,並求他們的和。

    在計算其餘區的時候,因爲該方法打印的結果能看到每一個函數,每一個變量屬於什麼區,佔多少內存,所以能夠精確的計算出size大小。

 

2. 藉助於連接器選項,生成map文件

 對於GCC,添加-Xlinker -Map=<filename>到連接器選項便可;對於ARMCC,添加-L--map -L--list=<filename>到連接器選項便可;對於MSVS,按照Linker->debugging->Generate Map Files -> Yes修改就能夠獲得可執行文件的map問價。咱們再分析map文件就能夠了。下面用gcc作實驗。

$ gcc -c hello-mac.c -o hello-mac.o
$ gcc -c mac.c -o mac.o
$ gcc -o hello-mac -Xlinker -Map=hello-mac.map hello-mac.o mac.o
$ grep "\.text.*mac.o" ./hello-mac.map | awk '{print $3}' | awk '{sum+=$1} END {print "sum=", sum}' # get .text memory

    經過查看map文件咱們能清晰的看出來哪一個源文件(在map中爲上面生成的.o目標文件)包含哪些函數,變量,各自佔了.text, .rodata, .data, .bss多少空間。這個方法對於全局變量,函數代碼區大小都能查到,可是,靜態變量並無被查到。

 3. 對比

 

  .text .rodata .data .bss
size
(hello-mac SUB empty-main)
425 64 176
objdump -x 113 44 32 104
map file 113 60 40 128
by hand \ 48 32 104

    結論:size方法雖然減掉了empty-main,但明顯仍是引入了其餘的glibc裏的東西,致使計算偏大;objdump -x可以得出最詳細且準確的信息,可是該方法因爲沒有給出每一個變量和函數屬於哪一個源文件,所以對於大型軟件的統計不利;map file的方法則是前面兩種方法的折中,既能快速自動化的算出結果,結果又很是接近真實值。

 三. 如何得到run-time memory consumption: heap, stack                                                              

    在上面的內存模型中,咱們會發現heap和stack是向着相反的方向增加,那麼,若是二者相遇重疊了會發生什麼?要麼發生heap的數據被stack覆蓋,或者相反。

    在調試程序時,經常會遇到「Segment Fault」, 「Stackoverflow", "Heap crash」, 最多見的緣由就是在於此。那麼

I.  是否能在程序運行時獲取程序當前的stack, heap大小,以及stack, heap的總容量呢?

II. 有時一個平臺上出現SegFault,可是在另外一個平臺就沒有了,如數組越界訪問,爲何?

    這部分還不知道有什麼工具能看。TBD

 

四. 參考                                                                                                                   

http://blog.sina.com.cn/s/blog_af9acfc60101bbcy.html

http://blog.csdn.net/gl23838/article/details/7924254

http://www.geeksforgeeks.org/memory-layout-of-c-program/

相關文章
相關標籤/搜索