C語言變數的儲存布局

2021-09-06 15:52:25 字數 3706 閱讀 6371

分析以下**中變數儲存空間如何分配: 

1

**無意義,僅供分析用

2 #include 3 #include //

malloc函式宣告位於或標頭檔案中

4 #include 56

#define len_test_int 4

7#define len_test_char 2089

int gbufinitzero[len_test_int] = ;

10int gbufinitnonzero[len_test_int] = ;

11int

gbufuninit[len_test_int];

1213

const

int gcvarinitnonzero = 10; //

const變數定義時必須初始化,因為定義後再不能改

14int gvarinitzero = 0;15

int gvarinitnonzero = 20;16

intgvarinituninit;

17static

int gsvarinitzero = 0;18

static

int gsvarinitnonzero = 30;19

static

intgsvaruninit;

2021

char gszstrinited = "

str_inited";

22char *gpszstrinited = "

psz_inited";

2324

int main(void

)

memseg

編譯生成a.out檔案(gcc memseg.c -g),通過readelf命令(readelf -a a.out)檢視目標檔案中各變數的位址分布。截圖中為突出重點對符號表結果進行了重排和擷取:

圖中,num列是符號表序號(已重新排列)。value列是符號位址,對於可重定位目標檔案表示距定義目標檔案的節(section)起始位置的偏移,對於可執行目標檔案表示絕對執行位址。size列為符號位元組大小。type列指示資料(object)、函式(func)或節(section)等型別。bind列表明符號的繫結資訊,分為區域性(對於目標檔案的外部不可見)、全域性(外部可見)和弱引用(weak)。ndx列為數字時表示節索引,為abs時代表不該被重定位的符號(如檔名),und代表定義在別處的符號(如printf庫函式),com代表未初始化資料目標(如某些編譯器的未初始化全域性變數)。name列為符號名(圖中所示為變數名)。

全域性變數gcvarinitnonzero用const修飾,表明不應修改其值。該變數被分配0x80485b0位址,從readelf輸出可知該位址位於.rodata段(區域性唯讀變數位於棧區):

其中0x5a0位址處的0a 00 00 00(小字節序)即變數gcvarinitnonzero的值。也可看到字串字面值"psz_inited"、"pszchar: %p(%s)\n"及"hello world"分配在.rodata段。

.data段從位址0x80496fc開始,長度是0x30,即到位址0x804972c結束。.data段存放初值不為0的非const全域性變數或靜態區域性變數。gvarinitnonzero是個global符號,而gsvarinitnonzero被static關鍵字修飾而成為local符號(不被鏈結器處理,即不能被其他檔案引用)。靜態區域性變數svarinitnonzero.2443只在函式內起作用,故編譯器對其符號名附加字尾,以便與同名的全域性變數或其它函式的變數區分。

注意,若字元陣列定義在函式外,則初始化字串存放在.data段(如"str_inited");否則存放在.rodata段(如"hello world")。——不解???

.bss段從位址0x804972c開始(緊挨.data段),長度為0x38,即到位址0x8049764結束。.bss段存放未初始化或初值為0的全域性變數或靜態區域性變數,如gvarinituninit、gvarinitzero及gsvaruninit等。

程式載入執行時,.rodata段和.text段通常合併為乙個segment,作業系統將該segment頁面唯讀保護起來,防止被意外改寫。.data和.bss段在載入時也合併為乙個segment,該segment可讀可寫。這點從readelf輸出也可看出:

函式的引數和區域性變數分配在棧上,故字元陣列szstrinited也在棧區。如下反彙編**所示:

可見,對棧區字元陣列賦值時,利用暫存器從全域性資料區把字串拷貝到棧記憶體中。szstrinited陣列的棧上內容從低位址到高位址依次為:hell(ebp-0x1c)、o wo(ebp-0x18)、rld\0(ebp-0x14)。

棧區從高位址向低位址增長,但陣列則從低位址向高位址排列,陣列元素szstrinited[n]的位址 = 陣列基位址(szstrinited做右值即表示基位址) + n × 每個元素的位元組數。當n=0時,元素szstrinited[0]的位址即為陣列基位址,因此陣列下標從0開始。

變數rvarinitnonzero並未在棧上分配儲存空間,而是直接存入ebx暫存器,後面呼叫printf直接從ebx暫存器裡取出變數值當作引數壓棧。因此,register關鍵字用於指示編譯器盡可能分配乙個暫存器來儲存該變數。此外,呼叫printf時對格式化引數"pszchar: %p(%s), rvarinitnonzero:%d\n"壓棧的是其.rodata段中的首位址(0x80485c0),並未壓入整個字串。故字串使用時可視為陣列名,若作為右值則表示陣列首元素位址(即指向陣列首元素的指標)。

pszchar和pszcharnext指標本身在棧上分配空間,但卻指向malloc動態分配的堆記憶體。因為堆空間從低位址向高位址增長,故pszcharnext>pszchar(堆),而&pszcharnext

最後,main函式的彙編指令存放在.text段。

【其他工具】

通過size命令可檢視elf檔案各段占用的位元組空間:

其中,dec和hex項分別為**段、資料段和bss段以十進位制和16進製表示總位元組數。

通過nm工具可檢視按段分布的變數儲存資訊(比readelf結果易讀):

通過objdump工具也可方便地檢視目標檔案中.rodata和.data段的內容(.bss段不寫入目標檔案):

objdump -t a.out的輸出結果與nm –ans類似。

此外,通過反彙編檔案memseg.s(gcc -s memseg.c)也可檢視.bss、.data和.text及棧區所存放的變數。

注:本文參考《linux c程式設計一站式學習》一書相應章節,在此致謝!

彙編與C語言關係 3 變數的儲存布局

以下面c程式為例 include const int a 10 int a 20 static int b 30 int c int main void 我們在全域性作用域和main函式的區域性作用域各定義了一些變數,並且引入一些新的關鍵字const,static,register來修飾變數,那麼這...

c語言變數儲存

記憶體中供使用者使用的儲存空間分為 區與資料區兩個部分。變數儲存在資料區,資料區又可分為靜態儲存區與動態儲存區。靜態儲存是指在程式執行期間給變數分配固定儲存空間的方式。如全域性變數存放在靜態儲存區中,程式執行時分配空間,程式執行完釋放。動態儲存是指在程式執行時根據實際需要動態分配儲存空間的方式。如形...

C語言變數的儲存類別

前面已經介紹了,從變數的作用域 即從空間 角度來分,可以分為全域性變數和區域性變數。從另乙個角度,從變數值存在的作時間 即生存期 角度來分,可以分為 靜態儲存方式 和動態儲存方式。使用者儲存空間可以分為三個部分 程式區 靜態儲存區 動態儲存區。全域性變數全部存放在靜態儲存區,在程式開始執行時給全域性...