1. 程式人生 > >從記憶體角度解釋C語言變數屬性

從記憶體角度解釋C語言變數屬性

1.儲存類
描述C語言變數儲存在記憶體中的地方。
記憶體有多種管理方法:棧,堆,資料段,bss段,.text段(程式碼段)
(1)區域性變數分配在棧上。
(2)顯示初始化為非0的全域性變數,分配在資料段
(3)沒有初始化(預設為0)的全域性變數分配在bss段

2.作用域
描述這個變數起作用的範圍

3.生命週期
什麼時候誕生(執行時分配記憶體空間給這個變數)
什麼時候死亡(執行時收回這個記憶體空間,或者訪問這個記憶體地址已經和這個變數無關了)
4.連結屬性
從原始碼到最終可執行程式經歷的過程:編譯,連結
編譯:就是把原始碼搞成.o目標檔案,目標檔案裡面有很多符號和程式碼段,資料段,bss段等分段,已經處理好了
怎麼知道放哪個段?通過符號識別
符號:就是變成中的變數名,函式名等,執行時變數名,函式名能夠和相應的記憶體對應起來,靠符號來在做連結的。
.o的目標檔案連結生成最終可執行程式,其實就是把符號和相對應的段給連結起來。包含有內連結屬性,外連結屬性,無連結屬性

linux下C程式的記憶體映像:
<0x0804 8000 保留區域 就是作業系統自己保留的一些區域,內部程式執行的

=0x0804 8000 .init,.text(程式碼段),.redata(只讀段) 從可執行程式載入
.data,.bss(讀寫段) 從可執行檔案中載入
heap(執行時堆)通過brk/sbrk系統呼叫擴大堆,向上增長
空閒記憶體
<0x4000 0000
=0x4000 0000 檔案對映區
空閒記憶體
<0xc000 0000 stack(使用者棧) ESP指向棧頂
=0xc000 0000 核心虛擬儲存器 使用者程式碼不可見區域
程式碼段,只讀資料段
(1)對應著程式中的程式碼。
(2)只讀資料段就是在程式執行期間只能讀不能寫的資料,const修飾的常量就有可能存放在只讀資料段(但不同平臺不一樣)
資料段,bss段
(1)資料段:1.顯示初始化非0的全域性變數;2.顯示初始化非0的static區域性變數
(2)bss段:.顯示初始化為0(預設為0)或者未初始化的全域性變數;2.顯示初始化為0的或者未初始化static區域性變數
堆(如果後面給該變數賦值了,)
(1)C語言不會自動向堆中存放東西,需要手工操作。
檔案對映區
(1)檔案對映區就是程序打開了檔案後,將這個檔案的內容從硬碟督導程序的檔案對映區,以後就直接在記憶體中操作這個檔案,都寫完以後在儲存時將記憶體中的檔案寫到硬碟中去

(1)1.區域性變數分配在棧上;2.函式呼叫傳參過程
核心對映區
(1)作業系統核心程式對映到這個區域了
(2)對於linux中的程序來說,都以為這個系統中只有它自己和核心程式,認為0xc000 0000以下都是它自己的活動空間,以上都是核心程式空間
(3)
OS下和裸機下C程式載入執行的差異

儲存類關鍵字:
auto:
只有一個作用,修飾區域性變數,表示這個區域性變數是自動區域性變數,自動區域性變數分配在棧上,說明它不初始化那麼值就是隨機的,也說明了它的生命週期。
平時都用到,只是省略了。

static:
在C語言中有兩種用法,且彼此沒有任何關聯。
1.用來修飾區域性變數,形成靜態區域性變數
靜態區域性變數和非靜態區域性變數的區別:
本質:儲存類不同(例如決定了生命週期不同等)
非靜態區域性變數分配在棧上,而靜態區域性變數分配在資料段/bss段上。
2.用來修飾全域性變數,形成靜態全域性變數。
目的:使全域性變數由外連結變成內連結,只作用域當前c檔案,避免重名,
這種兩種用法區別在於連結屬性上不同。
分析:
1.靜態區域性變數在儲存類方面和全域性變數一樣(儲存類)
2.靜態區域性變數在生命週期方面和區域性變數一樣。(生命週期)
3.靜態區域性變數和全域性變數的區別:作用域,連結屬性。
4.靜態區域性變數的作用域是程式碼塊(和普通區域性變數一樣),連結屬性是無連線。
5.全域性變數作用是(檔案作用域和函式是一樣的),連結屬性是外連線。
6.靜態全域性變數的連結屬性是內連結,雖然是全域性變數,但只作用於當前c檔案。

register:
1.編譯器會盡量將變數它分配在暫存器中(讀寫效率高很多,但編譯器只是承諾將register修飾的變數放在暫存器,但不保證一定放在暫存器,因為暫存器數量有限,所以滿了會分配到記憶體)
使用場景:變數被反覆高頻率的使用,所以register是一種提升程式執行效率,但一般用得很少
cpu從記憶體拿資料(記憶體->cache快取->暫存器->cpu)

extern:
extern主要用來宣告全域性變數,告訴編譯器我這個變數在別的檔案中,將來在連結的時候連結器會在別的.o檔案中找到這個同名變數。
定義包含宣告。
volatile:
表示這個變數可以被編譯器之外的東西改變。
編譯器之內:指變數的值的改變是程式碼的作用。
編譯器之外:不是程式碼造成的,不是當前程式碼造成的,編譯器在編譯器當前程式碼無法預知。
比如說別的執行緒改了該變數的值,該變數為執行緒共用。
有什麼作用?
編譯在遇到volatile修飾的變數不會對該變數的訪問進行優化,就不會出現錯誤。
什麼叫編譯器優化:
例如:
int a,b,c;
a=3;
b=a;
c=b;
//無優化:記憶體讀3次,即將3讀到暫存器,然後寫給a,然後再讀a,寫到b,再讀b,寫到a
//編譯器優化:記憶體讀一次(只用讀一次3到暫存器,然後分別寫給abc),寫3次,提升程式效率
但有時候又必須要加volatile,避免編譯器優化,因為編譯器優化會導致執行上的錯誤,而且這種錯誤很難被發現。
總結:vllatitle避免錯誤優化,但使用後會降低效率,正確區分,該加的加,不該加的不加。

restrict:
在C99以後才支援,gcc支援。
1.restrict也是和編譯器的行為特徵有關
2.只用來修飾指標,修飾普通變數沒有意義。
3.作用和volatile相反,加上以後是讓編譯器優化,不加則不優化(也不是說編譯器就不優化了,而是某種情況下編譯器不敢優化)
例子:
int f(int* a,int* b){
*a = 0;
*b = 1;
return *a;(原本可以直接返回0的,但是編譯器不敢做優化,所以要加restric)
}

int f(int* restric a,int* restric b){
*a = 0;
*b = 1;
return *a;(此時返回值在編譯後是直接寫的0)
}

作用域詳解:
1.區域性變數的程式碼塊作用域
2.函式名和全域性變數的檔案作用域

變數的生命週期:
1.變數生命週期的意義:
(1)瞭解變數的行為特徵
2.棧變數的生命週期
(1)生命週期為臨時的,臨時的意思:程式碼執行過程中按照需要去建立,使用,消亡。
例如一個函式內的區域性變數,在函式返回的時候消亡。
換個角度看
一個函式內的區域性變數為什麼在函式外不能使用,或者是無連線的?
因為別人在使用它的時候它已經消亡了
區域性變數為什麼分配在棧上,或者說區域性變數為什麼是臨時的生命週期?
因為棧就是這樣的特點,臨時的
3.堆變數的生命週期
堆是由作業系統維護,程式只能去申請,然後使用後釋放。
生命週期從申請時誕生,然後使用,直到free消亡。
4.資料段,bss段變數的生命週期
(1)全域性變數的生命週期是永久的,永久的意思是程式開始誕生,程式終止消亡。
(2)全域性變數所佔用的記憶體是不能被程式自己釋放的。
5.程式碼段,只讀段的生命週期
(1)其實就是我們的程式碼,其實就是函式,它的生命週期是永久的。
(2)有時候放在程式碼段的不只是程式碼,還有const型別的常量,還有字串常量(看平臺,有時候放在只讀段)

1.符號儲存在哪?在程式碼段中嗎?然後通過連結到相應的記憶體段?
2.如果bss段中的變數在後面被賦值了,它會調到資料段嗎?還是隻是符號和記憶體段的連結變化了?

連結屬性:
程式由多個.c檔案和.h檔案組成
程式的生成過程就是編譯+連結。
編譯是將函式/變數等變成.o的二進位制機器碼。
連結是為了將各個獨立的二進位制函式連結起來形成一個整體的二進位制可執行程式。
編譯時以檔案為單位,而連結以工程為單位。
先編譯後連結

為啥不宣告函式或者變數就無法使用,其中一個原因:
不宣告就無法單個檔案進行編譯,編譯器就不知道你這裡引用的東西是啥。

連結屬性:外連結,內連結,無連結
1.外連結:普通的函式,全域性變數,其實就是整個程式範圍內,跨檔案
2.內連結:只能在當前c檔案內部範圍內進行連結,包括(static修飾的函式或者全域性變數)
3.無連結:這個符號不參與連結,所有的區域性變數(不管auto,static)都是無連結的

因為C語言沒有名稱空間namespace,所以使用了3種連結屬性的方法,來解決全域性變數同名問題。
使用static修飾,讓全域性變數/函式成為內連結屬性,使其中一個或2個為內連結屬性就行了。

總結:
普通變數分配在棧上,未初始化時值是隨機的,變數分配到的地址也是棧範圍內隨機的,作用域為程式碼塊,生命週期是臨時的,連結屬性為無連線。
靜態區域性變數分配在資料段(初始化非0)或者bss段(未初始化或初始化為0),資料段和bss本質無區別,只是優化以後的結果,資料段是需要將值賦值到資料段,
bss段不需要,因為都是0,程式就可以更快的載入,同時變數地址在載入器載入後就確定了,且不會變,作用域為程式碼段,生命週期是永久的,連線屬性為無連線。
靜態全域性變數和普通全域性變數唯一的差別:
將連結屬性由外連結變成內連結,是為了解決全域性變數重名問題,所以在能確定其它檔案不會用到的全域性變數一定要static修飾
巨集和inline函式(因為會直接展開在程式碼中)的連結屬性為無連線
儲存類決定生命週期,作用域決定連結屬性