分析以下**中變數儲存空間如何分配:
1memseg**無意義,僅供分析用
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
)
編譯生成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語言變數的儲存類別
前面已經介紹了,從變數的作用域 即從空間 角度來分,可以分為全域性變數和區域性變數。從另乙個角度,從變數值存在的作時間 即生存期 角度來分,可以分為 靜態儲存方式 和動態儲存方式。使用者儲存空間可以分為三個部分 程式區 靜態儲存區 動態儲存區。全域性變數全部存放在靜態儲存區,在程式開始執行時給全域性...