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

2021-09-08 19:13:04 字數 2403 閱讀 2329

以下面c程式為例:

#include const

int a = 10

;int a = 20

;static

int b = 30

;int

c;int main(void

)

我們在全域性作用域和main函式的區域性作用域各定義了一些變數,並且引入一些新的關鍵字const, static, register來修飾變數,那麼這些變數的儲存空間是怎麼分配的呢?我們編譯之後用readelf命令看它的符號表,了解各變數的位址分布。下面的清單中原作者把符號表按位址從低到高重新排列了,並且只擷取了我們關心的那幾行:

變數a用const修飾,表示a是唯讀的,不可修改,它被分配的位址是0x8048540,從readelf的輸出可以看到這個位址位於.rodata段:

其中0x540位址處的0a 00 00 00 就是變數a,我們還看到程式中的字串字面值"hello world %d\n"分配在.rodata段的末尾,字串的字面值是唯讀的,相當於在全域性作用域定義了乙個const陣列:

程式載入執行時,.rodata段和.text段通常合併到乙個segment中,作業系統將這個segment唯讀保護起來,防止意外改寫。這一點從readelf的輸出也可以看出來:

注意,像a這種const變數在定義時必須初始化。因為只有初始化才有機會給它乙個值,一旦定義之後就不能再改寫了,即不能再賦值。

從上面readelf的輸出可以看到.data段從位址0x804a010開始,長度是0x14,也就是到位址0x804a024結束。在.data段中有三個變數,a,b和a.1589。

a是乙個global符號,而b被static關鍵字修飾了,導致它成為乙個local的符號,所以static在這裡的作用是宣告b這個符號為local的,不被鏈結器處理,如果把多個目標檔案鏈結在一起,local的符號只能在某乙個目標檔案中定義和使用,而不能定義在乙個目標檔案中卻在另乙個目標檔案中使用。乙個函式定義前面也可以用static修飾,表示這個函式名符號是local的。

還有乙個a.1589是什麼呢?它是main函式中的static int a。函式中的static變數不同於區域性變數,它並不是在呼叫函式時分配在函式返回時釋放,而是像全域性變數一樣靜態分配,所以用"static"這個詞。另一方面,函式中的static變數的作用域和區域性變數一樣只在函式中起作用,比如main函式中的a這個變數名只在main函式中起作用,所以編譯器給它的符號加了乙個字尾以便和全域性變數a以及其他函式的變數a區分開。

.bss段從位址0x804a024開始,長度為0xc,也就是到位址0x804a030結束。變數c位於這個段。從上面的readelf輸出可以看到.data和.bss在載入時合併到乙個segment中,這個segment是可讀寫的。.bss段和.data段的不同之處在於.bss段在檔案中不佔儲存空間,在載入時這個段用0填充。所以全域性變數如果不初始化則初值為0,也分配在.bss段。

現在還剩下函式中的b和c這兩個變數沒有分析。函式的引數和區域性變數是分配在棧上的,b是陣列也一樣,也是分配在棧上的,我們看main函式的反彙編**:

可見,給b初始化用的這個字串"hello world"並沒有分配在.rodata段,而是直接寫在指令裡了,通過三條movl指令把12個位元組寫到棧上,這就是b的儲存空間,如下圖所示:

雖然棧是從高位址向低位址增長的,但陣列總是從低位址向高位址排列的,按從低位址到高位址的順序依次是b[0]、b[1]、b[2]......

陣列元素b[n]的位址 = 陣列的基位址(b做右值就表示這個基位址) + n x 每個元素的位元組數,當n=0時,元素b[0]就是陣列的基位址,因此陣列下標要從0開始而不是從1開始。變數c並沒有在棧上分配儲存空間,而是直接存在eax暫存器裡,後面呼叫printf也是直接從eax暫存器裡取出c的值當引數壓棧,這就是register關鍵字的作用,指示編譯器盡可能分配乙個暫存器來儲存這個變數。呼叫printf時對於"hello world %d\n"這個引數壓棧的是它在.rodata段中的首位址,而不是把整個字串壓棧。所以字串在使用時可以看做陣列名,如果做右值則表示陣列首元素的位址。

作用域這個概念使用於所有識別符號,而不僅僅是變數,c語言的作用域分為一下幾類:

對屬於同一命名空間的重名識別符號,內層作用域會覆蓋外層作用域的識別符號。命名空間可分為以下幾類:

識別符號的鏈結屬性有三種:

儲存類修飾符(storage class specifier)有以下幾種關鍵字,可以修飾變數或函式宣告:

上面介紹的 const 關鍵字不是乙個storage class specifier,雖然看起來它也修飾乙個變數宣告,但是在以後介紹的更複雜的宣告中 const 在語法結構中允許出現的位置和storage class specifier是不完全相同的。 const 和以後要介紹的 restrict 和 volatile 關鍵字屬於同一類語法元素,稱為型別限定符(type qualifier)。

變數的生存期(storage duration,或者lifetime)分為以下幾類:

c與彙編的關係

start是匯程式設計序的入口,main是c程式的入口?gcc 只是乙個 外殼而不是真正的編譯器,這真的c編譯器是 usr lib gcc i486 gun 4.3.2 cc1,gcc呼叫c編譯器 彙編器和鏈結器完成c 的編譯鏈結工作。usr lib gcc i486 linux gun 4.3.2...

組合語言的變數與編碼

組合語言採用了助記符號編寫程式,通過編譯器轉換成能夠被計算機識別和處理的二進位制 程式。基 各計數制中每個數字上可用字元的個數。權 數字 1 在數字不同的數字所代表的數值。因此可得 500.03125 1f4.08h 十六進製制數與二進位制數之間的轉換 整數部分由小數點向左每4位一組,若整數最高位的...

C語言變數的儲存類別

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