深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

卷首語:

C語言的強大之處,在於其對內存的控制能力。而內存的控制則是通過變量的控制來進行。

變量的內存空間來自於哪裡?堆、棧、還是靜態存儲區。以及各變量的作用域,是理解本文的關鍵。

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

C 抽象概念藍色文本藍色背景

引導題:下面的代碼合法嗎?猜猜m的值是多少?

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

猜猜: m的值是多少?

講解:內層定義覆蓋(hide)外部定義

第3行:聲明並定義一個全局變量 m, 初始值為30;
第6行:打印m的值,此時看到的是全局變量m;結果:m=30
第9行:定義一個局部變量m,其名同全局變量m,此時發生hide,內層m將覆蓋外層m;
int m=m;
實際等效為:
int m;
m=m;
所以,由於其實局部(自動)變量,且尚未賦值,所以結果是一個隨機數,可能是0,也可能是其它隨機值。
第10行: m++;實際作用的是局部變量m
第11行:代碼塊 } 右花括號出現,花括號內局部變量均被銷燬。
第12行:因為之前花括號內局部變量m,因作用域結束而被銷燬,順應的外層m已經恢復作用,所以打印的是外層m的值,m=10;
總結:要理解本題,就要先理解變量的作用域,什麼是全局變量,什麼是局部變量,以及其覆蓋機制。這經常用來做面試題,但實際寫代碼,我們應該避免這種寫法。
深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

例1:運行結果

看完了上面這個例子,相信你已經體會到C語言的細節部分,一個小小的變量,換個寫法,就把自己考住了,看樣子有許多要注意的地方,需要系統的學習一下!

作用域、鏈接及存儲時期

C語言的任何一個變量,均可以用三個要素進行描述:作用域、鏈接、存儲時期,三者的搭配組合,就出現了5種變量類別,如表一。(其實,還有第6種,後續介紹)

(1)作用域:即變量的有效範圍。如塊作用域(block scope)、文件作用域(file scope)、函數原型作用域。

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

表一:C語言的5種存儲類

(2)鏈接:任意變量必有其一:外部鏈接(external linkage)、內部鏈接(internal linkage)、空鏈接(no linkage)。

一個具有外部鏈接的變量,可以在任何地方使用。具有內部鏈接的變量,可以在文件內任意位置訪問。而無鏈接的變量,則其變量名不可被其作用域之外的地方訪問。

所以鏈接,其實質暗示著變量的存儲位置,具有鏈接的變量,均是編譯時就分配了內存,且存放在靜態存儲區,其存放位置,即地址,不會再改變。所以稱為有鏈接。自動變量(局部變量),則存儲在棧中,在函數(塊內)定義時被創建,函數(塊)結束,即被銷燬。所以不具備“鏈接”,即不具備程序全生命週期的固定存儲位置(地址)。

而鏈接又區分內部和外部,區別只在於,是否使用static關鍵字進行修飾。

特例:無鏈接的靜態變量

即在函數、代碼塊內部創建的static變量,它具有代碼塊作用域、存放在靜態存儲區,卻又無鏈接。

這類變量只在編譯的時候初始化一次。若無初始化則默認為0。執行的時候,初始化語句被跳過(同其他有鏈接的變量一樣,代碼已經在編譯時完成,所以不會在運行時再運行一次。)

但是我們知道存放在靜態存儲區的變量,其位置是固定的,也即是全生命週期可見,那為什麼是無鏈接呢?實質是變量的標識符(變量名),因為定義為局部,所以限定了其變量名的可見範圍,但我們可以通過外層指針間接訪問它。可用下圖實驗驗證。

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

(3)存儲時期:靜態存儲時期(static storage duration)和自動存儲時期(automatic storage duration)。如果一個變量是靜態儲存時期,則其在程序執行期間將一直存在。如是自動存儲時期,則在定義時創建,作用域結束時即銷燬。靜態存儲並非單單指通過static修飾的變量。作為無static修飾的全局變量,其也是具有靜態存儲時期的。

簡之:關鍵看變量的內存分配在哪個位置,靜態存儲區、棧,還是堆。

聲明與定義的區別,何謂聲明,何謂定義?

舉個例子,來看下給初學者費勁腦子的聲明和定義,究竟有何區別?

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理
第3行:定義了一個名叫m的全局變量,為其分配了內存空間,並初始化賦值。稱之為定義式聲明(defining declaration)。記住,關鍵!是分配了內存。
第6行:聲明,告訴編譯器,我要引用一個其他地方定義過的名叫m的變量。不會產生內存分配,這類聲明稱之為引用聲明(referencing declaration)。

什麼是棧、堆、靜態存儲區

堆棧經常放在一起稱呼,但是堆和棧其實是不一樣的東西。在這裡就要引出我們之前提到的第6種存儲類型。這類存儲沒有名字,其產生和控制均由程序員控制。

第6個存儲類,來之於堆的malloc()類型

就是malloc()分配的空間類型,需要賦值給一個指針變量才能間接使用,用完需要free(),否則產生內存洩漏。所分配的空間在free()之前,都是可用的,只要你記住它的地址。malloc()的內存,來自於堆(heap)

堆(heap)

一個存儲區域,所分配的是連續的地址,會分配失敗,優點是空間大,動態分配。

malloc()的關鍵是所分配的內存來自於堆!

缺點是但頻繁的malloc/free會導致內存碎片產生,導致系統運行效率降低,且堆的分配速度遠慢於棧的分配,只不過,棧的空間相對較小。如有不但會導致棧溢出。

棧(stack)

棧是一塊連續的內存區域,大小是操作系統預定好的,在程序進程創建的時候分配。

棧的使用遵循FILO先進後出的原則,在數據結構這門課了對它的算法有著重的介紹。

棧主要用於存儲自動變量,也就是局部變量、函數返回值等等。優點是速度快、效率高。CPU一般都有入棧及出棧指令,所以其效率是指令級別的。缺點就是空間太小(2MB左右)。

靜態存儲區

全局變量、static修飾的變量以及const修飾的常量,以及字符串常量均儲存在靜態儲存區。所謂靜態,不是指static,而是指其空間和位置是固定的,並且在編譯時就已經決定。

總結,綜合下舉個例子

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

綜合,舉個例子做總結

可以看到其中的字符串常量P1和P2,地址是相同的,但P3則是一個局部變量,創建在棧上,存儲了一個和P1、P2相同的字符串。

深入解讀C語言的變量(一):作用域、鏈接、存儲時期及內存管理

執行結果

卷尾語:

C的這些變量涉及的內容同樣適用於C++;以及部分適用於其它語言。編程語言都是相通的,學會C之後,學習其他語言也就非常容易了。

時間有限,auto, register, static, extern;volatile, restrict, const將在篇二中做講解。

歡迎校正,評論,有疑問的可以私信,每天都會抽空回答。


分享到:


相關文章: