【轉】C數據存儲(包括const存儲在哪,C++不一樣部分我在文中用紅字已指出)

非原創(文中紅字爲本身看法,若有不對,請大神指點)html

  程序由指令和數據組成,C語言程序亦是如此。開發者在編寫程序的時候每每須要根據不一樣數據的特色以及程序需求來選擇不一樣的數據存儲方式,那麼在C語言中數據的存儲分爲哪些方式呢?編程

C程序大體來說能夠分爲四個數據區:常量區,靜態去,堆區,棧區。數組

  其中常量區存儲了未被做爲初始化使用的字符串常量和被const修飾的全局變量,其特色是隻可被訪問不可被寫入,生命週期同程序的運行過程。安全

  靜態區存儲了所有的全局變量,和全部被static修飾的變量(包括全局和局部),其特色是生命週期很長(爲一次程序的運行過程)而且只被初始化一次(在編譯以後就已完成)。架構

  棧區存儲了全部自動存儲(不加任何存儲類型關鍵字修飾或被auto修飾)的局部變量,其特色是生命週期很短,僅僅是該變量所在函數的一次調用過程。運行時有操做系統分配並在函數結束後回收。函數

  堆區是由操做系統負責維護的大片內存池,使用時需手動申請(調用malloc家族函數),但使用完畢後需手動釋放,不然會形成嚴重的內存泄漏,直到該進程退出後纔會被操做系統回收。優化

  下面詳細介紹每種存儲類型的特色。spa

一.常量區:操作系統

  故名思意,常量區裏存放的是一些不可改變的量,好比字符串常量。在實際的ELF(Executable and Linkable Format,可執行鏈接格式,是UNIX系統實驗室(USL)做爲應用程序二進制接口(Application Binary Interface,ABI)而開發和發佈的。Linux 做爲類unix系統其程序仍然沿用了該格式。)的程序數據是分段存儲的,對應的常量區就是".rodata(只讀數據)段"。3d

  常量區的數據被標記爲只讀,也就是程序只有訪問權而沒有寫入權,所以若是開發者須要使用某些不但願被改變的數據時能夠將其放入常量區。

  在C語言中常量有不少種,好比常見的:

字符常量:'a', 'A', '*'。

字符串常量:"helloworld","ilovechina","12345"。

整常量: 25,10,012,0x0a,0b00001010。

浮點常量: 3.14,123.456, 3.0E-23;

  可是並非全部的常量都會被編譯器放在常量區的,如圖1-1中代碼所示:

圖1-1 定義一個變量並被常量初始化

  圖中程序定義了一個整型局部變量i,而且被初始化爲10,其中i是變量,10是常量,可是編譯器並不將10放入常量區,而是在指令中直接經過當即數賦值(圖1-2)。

圖1-2 由圖1-1程序編譯生成的彙編代碼

  這是由於編譯器認爲普通的整型、浮點型或字符型常量在使用的時候是能夠經過當即數來實現的,沒有必要額外存儲到數據區,如此節省了存儲空間和運行時的訪問時間。那麼什麼樣的數據纔將放入常量區呢?

1.字符串常量

  如圖1-1所示,在C程序中定義了一個局部的字符指針變量p指向一個字符串常量,其中p因爲是局部變量被放在棧區,而字符串常量"helloworld"在彙編中被放入.rodata段(圖1-2),在編譯後生成的ELF格式文件中也將被放入.rodata段(圖1-3)

圖1-3 C語言的示例程序定義指針變量指向字符串常量。

圖1-4 由圖1-3中代碼生成的彙編程序。

圖1-5 由圖1-3中程序編譯生成的可執行程序分析。

  可是,當一個字符常量串被用來爲數組初始化的時候,那麼該字符串常量將不會放入常量區,而是放入對應的數組中,如圖1-6所示:

圖1-6 定義一個字符數組並用字符串常量初始化

  編譯器會將該字符串按照四字節爲一組轉換成對應的32位整數來爲該數組進行初始化,如圖1-7所示,其中第13行的10進制整數1819043176轉換成16進製爲0x6c6c6568,正好是字符'l','l','e','h':

圖1-7 圖1-6中C代碼生成的彙編程序

  所以在編譯生成的ELF格式文件中的.rodata段也將不會存儲該字符串常量:

圖1-8 圖1-6程序編譯後生成的可執行程序片斷。

2.被const修飾的全局變量

a)

  除了字符串以外,其餘常量也能夠放在常量區,可是前提是該數據必須被存放在全局變量的空間裏,而且被const關鍵字修飾。如圖1-9代 碼所示:

圖1-9 第4行定義了一個被const修飾的全局變量

  編譯生成的彙編程序比較:

圖1-10

  其中value0因爲被const修飾因此放在了.rodata段也就是所謂的常量區,而value1是一個普通的全局變量因此放在了.data段也就是所謂的靜態數據區。分析編譯生成的ELF格式可執行程序以下:

圖1-11 value0的存放位置

  其中value0的數據被放在常量區(.rodata段)十六進制顯示的0a對應了它的十進制初始值10。

圖1-12 value1的存放位置

  Value1的數據被放在靜態區(.data段)十六進制顯示的14對應了它的十進制初始值20。

b)

  可是並非全部被const修飾過的變量都放在常量區,事實上只有全局變量纔是如此,普通的局部變量被const修飾後僅僅意味着在表達式上不能顯式地改變該變量值,不然編譯器會報語法錯誤,但該變量仍存放在棧區。C++不一樣的地方就在此,C++鼓勵使用const來取代#define,由於C++對const進行了優化,若是該變量的值是常量表達式,在C++中就會進行常量摺疊(constant folding),何爲const folding,百度一大堆,簡單來講就是在編譯期間任何出現該變量的地方都會被替換成常量表達式的值,正所以,這種狀況下const定義的變量是能夠用於定義數組的維度的,而C語言就沒有這個優化特性,因此C語言的const修飾的變量是不管如何都不能進行數組維度的定義的(注意,C++中不進行常量摺疊的狀況下,const修飾的仍沒法進行數組定義,具體何時常量摺疊請參看個人博文http://www.cnblogs.com/yanqi0124/p/3795019.html。而因爲其存儲區域沒有發生本質的改變,所以仍然能夠經過其餘方式改變其值,好比指針。如圖1-13所示:

圖1-13 定義兩個局部變量,其中一個被const修飾

  保存,編譯,結果以下:

圖1-14 編譯器發生編譯錯誤

  因爲 value1被const修飾,因此程序第9行的賦值語句將發生錯誤。

  接着修改程序,經過指針去修改value1的值:

圖1-15 定義指針p指向value1並經過指針賦值。

  編譯,運行:

圖1-16 編譯運行結果

  因爲定義的指針變量p和表達式&value1的類型不匹配(p是int *,而&value1的類型是const int *)因此在第7行賦值的時候編譯器會產生一個類型不匹配的警告,咱們忽略該警告繼續運行,結果改變了value1的值。

3.由常量區引起的段錯誤

  因爲常量區的特性是隻讀,所以當程序試圖去向指向常量區的地址寫入數據的時候,操做系統處於安全考慮會發出一個段錯誤的信號而且殺死該進程,以達到保護操做系統的目的。

圖1-17 示例代碼,經過指針去向常量區寫數據。

  第10行和11行的都可產生一樣的段錯誤,如圖1-18所示

圖1-18 非法寫入引起的段錯誤

二.靜態區:

  靜態區是一個抽象籠統的概念,在實際的Linux/C的可執行程序中並無靜態區這個區域,具體來說它主要由兩個段組成:.data段和.bss段。其中.data段就是程序的數據段,在採用段式內存管理的架構中,數據段(data segment)一般是指用來存放程序中已初始化且不爲0的全局變量或靜態變量的一塊內存區域。相反,BSS(Block Started by Symbol)一般是指用來存放程序中未初始化的或初始化爲0的全局變量或靜態變量的一塊內存區域。.data段在程序編譯期間其大小及數據被肯定,而.bss段則沒有直接分配空間而是由編譯器在.data段以後爲其預留空間,在程序裝載進內存時被正式分配。儘管靜態區由兩個不一樣的段組成,可是在程序連接並裝載進內存以後這兩段不作區分,所以咱們在這裏不作分開討論。

  靜態區的變量擁有如下特徵:

1) 生命週期長,直到該進程結束隨進程空間一塊兒被系統回收。

2) 只初始化一次,它的空間數據在編譯期間被初始化,邏輯地址在連接期間固定。

那麼哪些變量將被放在靜態區呢?

1.全局變量:

  顧名思義它是全局的公用的,若是一個變量被定義爲全局的,那麼在同一個程序中,任何函數均可以去訪問、存取該變量的數據。基於此,全局變量除擁有靜態區變量的所有特徵以外還具備做用域廣的特色,其做用域在整個程序中(能夠由多個源文件組成)全局可見。

2.靜態變量

  從字面上理解所謂靜態變量就是被static關鍵字修飾的變量,只要被static修飾爲靜態變量那麼都將被編譯器分配在靜態區,其也就擁有了靜態區變量的所有特徵。靜態變量分兩種:全局靜態變量和局部靜態變量。不管哪一種只要被static修飾都將放在靜態區,擁有靜態區變量的所有特徵。其區別僅在於做用域:若是是全局靜態變量,那麼該變量的做用域被限定只能在本源文件內使用(編譯以後該變量的符號將不容許對外連接,可是仍然能夠經過指針去間接訪問);若是是局部變量則沒有變化(僅限函數內部使用)。

  下面給出一段示例代碼用以說明靜態變量的特性(圖2-1):

圖 2-1

  代碼中定義了一個全局變量gvalue和局部變量lvalue,而且通過兩次函數調用。gvalue因爲是全局變量被編譯器分配在靜態區,而lvalue是局部變量放在棧區。因爲靜態區的特寫致使gvalue通過兩次函數調用實現了累加,而局部變量lvalue則每次在函數調用時都被從新初始化。地址(圖2-2)。

圖 2-2

  下面更改程序,將局部變量修改成靜態局部變量(圖2-3):

圖 2-3

  則因爲局部變量lvalue被static修飾放在了靜態區只初始化一次,所以也實現了累加(圖2-4)。

圖 2-4

原文出處:

淺談C語言的數據存儲(一):http://www.embedu.org/Column/Column540.htm

淺談C語言的數據存儲(二) :http://www.embedu.org/Column/Column558.htm

相關文章
相關標籤/搜索