全域性記憶體BSS,DATA,RODATA的區別以及其他記憶體區間相關

2020-08-11 20:35:45

剛接觸c語言的時候,瞭解了全域性變數這個概念,只知道所謂全域性變數的意思就是程式碼檔案裏面所有的函數都可以隨時呼叫修改的變數,而其實這種理解是十分不準 不準確的,但隨着後期學習的深入,接觸了計算機組成原理和彙編的相關知識,通過閱讀其他大神的部落格,學到了很多,發現所謂全域性變數其實是存放在全域性記憶體中的變數,以及很重要的一點就是強調全域性,這個所謂的全域性指的是變數的生命週期,而不是他的作用域,先前的理解誤區正是將全域性理解爲僅指作用域。

static:使用static宣告的變數,他可能是在某一個函數中進行申明,這時可以說這個變數的作用範圍是區域性的,但是生命週期是全域性的。

一般而言,定義在一起的兩個全域性變數在記憶體中位置是相鄰的,這局簡單的話有時很有用,比如一個全域性變數受到了破壞,那麼程式中定義位置前後陣列或是其他元素可能出現了越界存取,程式碼排錯的時候可以快速縮小錯誤位置區間。

BSS:

如果一個全域性變數沒有被初始化 或者被初始化爲0,那麼這個全域性變數就被存放在bss記憶體中。

這種型別的全域性變數的特點:
作如下變數宣告:
int bssTest[1024*1024]={0};

編譯鏈接結束後,檢視可執行檔案大小,遠小於4M,這說明bss類的全域性變數不佔用檔案儲存空間而只佔用程式執行記憶體空間

對於大多數的操作系統,在程式載入的時候會自動的將在所有bss全域性變數清零,無需手動清零,但是手動清零是一種很好的程式設計習慣

DATA:

正如其名:數據。
data記憶體放那些初始化過的非const全域性變數,當然如果初始化爲0,編譯器還是會被把他當作bss來處理。
對於其特點,與BSS對應來看,同樣在程式碼中也做如下宣告:
int dataTest[1024*1024]={1};

編譯鏈接結束檢視可執行檔案大小發現大小爲4M+,於是,data全域性變數佔用檔案空間也佔用執行記憶體空間

RODATA

:所謂RO,read only,只讀,也就是隻允許讀取而不允許修改的意思。
對於RODATA類的數據:

  • 常數不一定就放在RODATA內, 有的立即數直接編碼在指令裡,存放在程式碼段中(text)
  • 字串常數在經過編譯器處理的時候會被自動去掉重複字串,保證一格字串在一個可執行檔案中只有一份拷貝。
  • RODATA是 多執行緒共用記憶體 ,這樣做的好處就是可以提高空間的利用率。
  • 有的嵌入式系統中,rodata放在ROM中(norflash),執行時直接載入ROM而無須載入到RAM中。
  • 嵌入式linux系統中有一種技術叫做 XIP(就地執行) ,同樣能實現直接讀取而無須載入到RAM中。

把執行過程中不會發生改變的數據放到rodata區是有很多好處的,多執行緒共用,提升空間利用率甚至不佔用RAM空間,以及rodata的只讀屬性在某些情況下可以阻止意外的數據破壞,能夠提升程式執行的穩定性


上面提到了static關鍵字,其實static關鍵字的作用就是改變生命週期限製作用域

  1. 修飾行內函式(inline func):限製作用域。
  2. 修飾普通函數:限製作用域。
  3. 修飾區域性變數:改變生命週期。
  4. 修飾全域性變數:先知作用域。

對於const關鍵字:

使用const的常數放在rodata裏面,字串預設是常數:有如下定義:

char *p = 「hello world!」;
const char *p = 「hello world!」;

這兩種宣告,無論是上者還是下者,當試圖使用p指針去修改字串內容的時候都會提示錯誤,字串所在位置都是靜態區,但是p指針本身所在位置有區別

這個就涉及了另外的問題:

  • 指針常數和常數指針的問題(修飾就近原則):

指針常數:指向的數據是常數,這裏我做如下定義:
const char *p = {hello};//這種定義下const修飾char *p也就是p指針指向的內容,既p指向的字串作爲常數放在bss,而指針p並不是常數,我們還可以改變他的指向,讓它指向別的字串。

常數指針,顧名思義,指針是常數,定義方式如下:
char *const p = {hello};//p本事就是一個常數,只能指向hello\0這個字串而不能改變。

記憶體的其他區域:

heap堆區:這個區域用來儲存使用malloc分配的記憶體空間,使用完以後需要使用free手動釋放來管理儲存空間,雖然程式退出後OS也會自動釋放。
stack棧區:這一部分的儲存空間是編譯器在編譯程式碼的時候就分配好點的固定儲存空間