存儲類&做用域&生命週期&連接屬性

4.7.1概念解析

4.7.1.一、存儲類

  • (1)存儲類就是存儲類型,也就是描述C語言變量在何種地方存儲
  • (2)內存有好多中管理方法:棧、堆、.data段、.bss段、.text段..........一個變量的存儲類屬性就是描述這個變量存儲在哪一個內存段中。
  • (3)譬如:局部變量分配在棧上,因此它的存儲類是棧;顯示初始化爲非0的全局變量分配在數據段。顯示初始化位0和沒有顯式初始化(默認是0)的全局變量分配在.bss段。

4.7.1.二、做用域

(1)做用域是描述這個變量起做用的代碼範圍html

(2)基原本說,C語言的做用域規則是代碼塊做用域。意思是這個變量其做用的範圍是當前的代碼塊。代碼塊就是一堆大括號{ }範圍內,因此這個變量的做用域是:這個變量定義所在的 { } 內從這個變量定義開始日後的部分(這就解釋了爲何變量定義老是在函數的最前面)linux

#include <stdio.h>

int var = 10;

int main(void)
{
    int var = 5;
    if(1)
    {
        int var = 2;
        printf("var = %d.\n",var);
    }
    
    printf("var = %d.\n",var);
    return 0;
}
運行結果:
var = 2.
var = 5.

4.7.1.三、生命週期

  • (1)生命週期是描述這個變量何時誕生(運行時分配內存空間給這個變量)以及何時死亡(運行時收回這個內存空間,伺候再不能訪問這個內存地址,或者這個內存地址已經和這個變量無關了)
  • (2)變量和內存的關係,就和人(變量)去圖書館(DRAM)借(申請)書(內存)同樣。變量的生命週期就好像人借書的這段週期同樣。
  • (3)研究變量的生命週期能夠幫助咱們理解程序運行的一些現象,理解C語言的一些規則。

4.7.1.四、連接屬性

  • (1)你們知道程序從源代碼到最終可執行程序,經歷的過程:編譯、連接
  • (2)編譯階段就是把源代碼編譯成.o目標文件,目標文件裏有不少符號和代碼段、數據段、bss段等分段。符號就是編程中的變量名函數名等。運行時,變量名和函數名可以和相應的內存對應起來,靠符號來作鏈接的。
  • (3).o的目標文件連接生成最終可執行程序的時候,其實就是把符號和相對應的段鏈接起來。C語言中的符號有三總連接屬性:外連接屬性、內鏈接屬性、無連接屬性

總結:以上4個概念,其實就是從4個不一樣角度分析C語言的一些規則。綜合這4個分析角度,可以讓程序員徹底掌握C語言程序的運行規則和方法。程序員

4.7.2linux下C程序的內存映像

4.7.2.一、代碼段、只讀數據段

  • (1)對應着程序中的代碼(函數),代碼段在linux中又叫文本段(.text)。
  • (2)只讀數據段就是在程序運行期間只能讀不能寫,const修飾的常量就有多是存放在只讀數據段的(可是不必定的,const關鍵字在不一樣的平臺實現方法是不同的)

4.7.2.2數據(.data)段、bss段

  • 數據段:顯示初始化爲非0的全局變量、顯示初始化爲非0的static局部變量。
  • bss段:顯示初始化爲0或者未顯示初始化的全局變量,顯示初始化爲0或者維顯式初始化爲0的static局部變量。

4.7.2.三、堆

  • (1)C語言中什麼變量存在堆內存中?C語言不會自動向堆內存中存放東西,堆的操做是程序員本身手工操做的。程序員根據需求本身判斷要不要使用堆內存,用的時候本身申請,本身使用,使用完本身釋放。

4.7.2.四、文件映射區

  • (1)文件映射區就是進程打開文件後,將這個文件的內容從硬盤的內容讀到進程的文件映射區,之後就直接在你的內存中操做這個文件,讀寫完了之後在保存時再將內存中的文件寫到硬盤中。

4.7.2.五、棧

  • (1)棧內存區:局部變量分配在棧上;函數調用傳參過程也會用到棧。

4.7.2.6內核映射區

  • (1)內核映射區就是將操做系統內核程序映射到這個區域了
  • (2)對於linux中的每個進程來講,它都覺得整個系統中只有它本身和內核而已,它認爲內存地址0xC0000000如下都是它本身的活動空間,0xC0000000以上是操做系統(OS)內核的活動空間
  • (3)每個進程都活在本身獨立的進程空間中,0~3G的空間每個進程是不一樣的(由於用了虛擬地址技術),可是內核是惟一的。

4.7.2.七、OS下和裸機C程序加載執行的差別

  • (1)C語言運行時環境有必定要求,意思是單獨我的寫的C語言程序無法直接在內存中運行,須要外部必定的協助,這段協助代碼叫作加載運行代碼(或者說構建C語言運行時環境代碼,這一段代碼是操做系統下被人寫好的,會自動添加到咱們寫的程序上,這段代碼的主要做用是:給全局變量賦值,清bss段)
  • (2)ARM裸機十六部分,寫shell時 有一次定義了全局變量初始化爲0了,可是實際上不爲0,後來在裸機的start.S中加了清bss段就變0 了。這就說明了在裸機程序中沒人幫咱們作這一段程序運行時代碼,要程序員本身作(start.S中的重定位和清bss段就是作這個事),在操做系統中,運行程序時,程序員本身不用操心,會自動完成重定位和清bss段,因此咱們看到的現象是:C語言中初始化的全局變量默認爲0.........。
  • (3)數據段的全局變量或靜態局部變量都是有非0的初值,在main函數運行以前就已經被初始化了。是重定位期間完成的初始化。

4.7.三、存儲類相關的關鍵字1

4.7.3.一、auto

  • (1)auto關鍵字在C語言中只有一個做用。修飾局部變量
  • (2)auto修飾局部變量,表示這個局部變量是自動局部變量,自動局部變量分配在棧上。(既然分配在棧上,說明它不初始化那麼值就是隨機的)
  • (3)平時定義局部變量時就是定義auto的,知識省略了auto關鍵字。可見,auto的局部變量其實就是默認定義的普通的局部變量

4.7.3.二、static

  • (1)static關鍵字在C語言中有兩種用法,並且在C語言中沒有關聯。徹底獨立的。其實當年本應該多發明一個關鍵字的,可是做者以爲關鍵字太多很差記因此給static多了一種用法,致使static一個關鍵字有兩種含義
  • (2)static的第一種用法:修飾局部變量,造成靜態局部變量。要搞清楚靜態局部變量和非靜態局部變量的區別。本質區別是存儲類不一樣(存儲類不一樣就衍生出不少不一樣)非靜態局部變量分配在棧上,而靜態局部變量分配在數據段或者是bss段上。
  • (3)static 的第二種用法:修飾全局變量,造成靜態全局變量。要搞清楚靜態全局變量和非靜態全局變量的區別,區別主要在連接屬性上不一樣,在連接屬性處詳細講。

分析:shell

  • 一、靜態局部變量在存儲類方面和全局變量同樣
  • 二、靜態局部變量在生命週期方面和全局變量同樣
  • 三、靜態局部變量個全局變量的區別是:做用域、連接屬性。靜態局部變量做用域是代碼塊做用域,和普通局部變量是同樣的。無鏈接屬性。而全局變量的做用域是文件做用域和函數是同樣的、連接屬性是外連接。

4.7.3.三、registe

  • (1)register關鍵字不經常使用,也只有一個做用,那就是:register修飾的變量編譯器會盡可能將他分配在寄存器中。(平時分配的局部變量都是分配在棧即內存中的)。分配在寄存器中同樣的用,可是讀寫效率會高不少。因此register修飾的變量用在哪些變量被反覆使用,經過改善這個變量的訪問效率能夠極大的提升程序運行的效率時。因此register是一種極致的提高程序運行效率的手段。
  • (2)uboot中用到了一個registe類型的變量,gd這個變量是用來存uboot中的全局變量(gd就是global data)由於這個全局變量在uboot中導出都被訪問,因此定義成register的
  • (3)平時寫代碼用到register的狀況不多,通常慎用。
  • (4)register編譯器只是承諾儘可能將register修飾的變量放在寄存器中,可是不保證必定放在寄存器中,主要緣由是寄存器是有限的,不必定有空的給你用。

4.7.4存儲類相關的關鍵字2

4.7.4.一、extern

  • (1)extern主要是用來聲明全局變量,聲明的目的主要是在a.c中定義變量,而在b.c中使用變量。
  • (2)C語言中程序的編譯是以單個.c的源文件爲單位的,所以編譯a.c時只考慮a.c的內容(不會考慮b.c的內容)這就致使了a.c中使用了b.c中的變量時在編譯時報錯。解決方案就是聲明
  • (3)應該在a.c中使用g_b以前聲明g_b,聲明就是告訴a.c我在別的文件中定義了g_b,而且它的原型和聲明的同樣,讓編譯器不要管了,在連接的時候會在其餘的.o文件中找到同名的變量。聲明一個變量的方法就是要用到extern關鍵字。

4.7.4.二、volatile

  • (1)volatile的字面意思:可變的、易變的。C語言中用來修飾一個變量表示這個變量能夠被編譯器以外的東西改變。編譯器之類的意思是變量的值的改變是代碼的做用,而編譯器以外的改變是指這個改變不是代碼形成的,或者說不是當前代碼形成的,編譯器在編譯當前代碼時沒法預知。
  • 譬如說在中斷處理程序isr中,更改的這個變量的值,
  1. 譬如多線程中更改的這個變量的值
  2. 譬如說硬件自動更改的這個變量的值(通常是寄存器)。
  • (2)以上說的三種狀況:中斷isr中引用的變量、多線程中共用的變量,硬件會改的變量
  • 編譯器在編譯是沒法預知的更改,此時應該使用volatile告訴編譯器這個變量屬於這種(可變的、易變的)狀況。編譯器在遇到volatile修飾的變量時就不會對這個變量的訪問進行優化,就不會出現錯誤。並且這種錯誤發生了不容易找到。
  • (3)編譯器的優化在通常狀況下很是好,能夠幫助提高程序效率,可是在特殊狀況下,變量會背編譯器想象以外的力量所改變,此時若是編譯器沒有意識到,而進行了優化則就會形成錯誤。優化錯誤就會形成執行錯誤。
  • (4)volatile是程序員意識到須要volatile而後再定義時加上volatile,若是你遇到了須要加volatile的狀況卻沒有加,程序可能會出現錯誤。若是在不須要加volatile的狀況加了volatile程序不會出錯,只是會下降程序的效率。因此對於volatile的正確態度是該加就加,不應加就不加,若是不能肯定就加。

4.7.4.三、restrict

  • (1)C99中才支持的,因此不少延續C89的編譯器是不支持register關鍵字,gcc支持的。
  • (2)restrict也是和編譯器行爲特徵有關的。
  • (3)resrtict只用來修飾指針,不能修飾普通變量。
  • (4)http://blog.chinaunix.net/uid-22197900-id-359209.html
  • (5)memcpy和memmove的區別

4.7.4.四、typedef

  • (1)以前講過了。
  • (2)typedef在C語言關鍵字歸類上屬於存儲類關鍵字,可是實際上和存儲類不要緊。

4.7.5做用域詳解

4.7.5.一、局部變量的代碼塊做用域

  • (1)代碼塊基本能夠理解成一對大括號{ }栝起來的部分
  • (2)代碼塊不等於函數,由於if   while   for  都有{ },因此代碼塊<=函數
  • (3)局部變量的做用因而代碼塊做用域,也就是一個局部變量能夠被訪問和使用的範圍僅限於定義這個局部變量的代碼塊中定義了變量以後的部分。

4.7.5.二、函數名和全局變量的文件做用域

  • (1)文件做用域的意思是全局的訪問權限,也就是在整個的.c文件中均可以訪問這些東西。這就是平時所說的局部和全局,全局就是文件做用域。
  • (2)詳細準確地來講就是:函數和全局變量的做用域就是定義所在的整個.c文件以內定義式以後的部分。

總結:編程

  • (1)無論是局部變量、全局變量、函數,都要先定義才能使用。
  • (2)嚴格來講咱們上面的總結是錯的,準確的說全局變量的做用域都是本身所在的代碼塊/文件,可是定義式以前的部分由於咱們缺乏聲明,因此無法在定義以前用。解決方案:一:把他定義到前面去     二:定義到後面可是在前面加聲明。局部變量因爲無法聲明,因此只能定義到前面去。
  • (3)在c89標準的編譯器中(如今不少的編譯器還在沿用c89),全部的局部變量必須定義在最前面,在變量定義前,不能有一句執行代碼。在c99標準的編譯器中(gcc兼容c99標準 )能夠容許在代碼塊任意地方定義變量,可是容許定義的變量仍是隻能使用在定義以後,定義以前仍是不能用的。

4.7.5.三、同名變量的掩蔽規則

  • (1)問題:編程時,不可避免會出現同名變量,變量同名後不必定會出錯
  • (2)若是兩個同名變量的做用域不一樣且沒有交疊,這種狀況下沒有任何影響。
  • (3)若是兩個同名變量做用域有交疊,C語言在做用域的交疊範圍內,做用域小的一個變量會掩蔽掉大的那個(縣官不如現管)

4.7.六、變量的生命週期

4.7.6.一、研究變量生命週期的意義多線程

(1)研究變量的生命週期,有助於理解變量的行爲特徵。架構

4.7.6.二、棧變量的生命週期

  • (1)局部變量(棧變量)存儲在棧上,生命週期是臨時的,臨時的意思是說:代碼執行過程當中按照須要去建立、使用、消亡的。
  • (2)譬如一個函數內定義的局部變量,在這個函數每一次被調用時都會被建立一次,而後使用,最後在函數返回時消亡。
  • (3)思考:一個函數內的局部變量爲何在函數外不能使用?
  • (4)思考:局部變量爲何分配在棧上?局部變量爲何是臨時生命週期?

4.7.6.三、堆變量的生命週期

  • (1)首先要明白:對內存空間是客觀存在的,由操做系統維護的,咱們程序只是去申請而後使用而後釋放。
  • (2)咱們只關心咱們程序使用堆內存的這段時間,所以堆內存也有本身的生命週期,就是:從malloc申請時開始,而後使用,知道free時消亡。
  • (3)因此堆內存在malloc以前和free以後都不能夠訪問,所以堆內存在實踐編程時都是反覆malloc和free的

4.7.6.4數據段、bss段變量的生命週期

  • (1)全局變量的生命週期是永久的。永久的含義是在程序執行時誕生,在程序終止時消亡
  • (2)全局變量所佔用的內存是不能被程序本身釋放的,因此程序若是申請了過多的全局變量,會致使這個程序一直佔用大量的內存。
  • (3)若是說堆內存是圖書館借書,那麼全局變量就是本身買書。

4.7.6.5代碼段、只讀段的生命週期

  • (1)其實就是程序執行的代碼,其實就是函數,他的生命週期是用永久的。不過通常代碼的生命週期咱們並不關注
  • (2)優點後放在代碼段的不僅是代碼還有const類型的常量,還有字符串常量有時候放在rodata段,有時候放在代碼段,取決於平臺。

4.7.7連接屬性

4.7.7.1C語言程序的組織架構:多個C文件+多個h文件

  • (1)龐大、完整的一個C語言程序(譬如linux內核、uboot)由多個c文件和h文件組成的
  • (2)程序的生成過程就是:編譯+連接。編譯是爲了把函數/變量編譯成.o二進制的機器碼格式。連接時爲了將各個獨立分開的二進制的函數鏈接起來造成一個總體的二進制可執行程序。

4.7.7.二、編譯以文件爲單位、連接以工程爲單位

  • (1)編譯器工做時是將全部源文件一次讀進來,單個爲單位進行編譯的。
  • (2)連接時其實是把第一步編譯生成的單個的.o文件總體的輸入,而後處理連接成一個可執行程序。

4.7.7.三、三種連接屬性:外連接、內連接、無連接

  • (1)外連接的意思是外部連接屬性,也就是這傢伙能夠在整個程序範圍內(言下之意就是能夠跨文件)進行鏈接,譬如函數和全局變量屬於外連接
  • (2)內連接的意思就是(C文件內部)內部連接屬性,這傢伙能夠在當前的C文件內部範圍內進行連接。(言下之意就是不能在該在當前c文件外部的C文件進行訪問和連接)static修飾的函數和全局變量都是內連接。
  • (3)無連接的意思就是這個符號自己不參與連接,連接的時候根本不用考慮他。他跟連接沒有關係,全部的局部變量(auto的、static的)都是無連接。

4.7.7.五、函數和全局變量的同名衝突

  • (1)由於函數和全局變量是外部連接屬性,就是說每個函數和全局變量,未來在整個程序中全部的C文件都能被訪問。所以,在一個程序中的全部的C文件中不能出現同名函數/同名的全局變量
  • (2)最簡單的解決方案就是起名字不要重複,可是很難作到,主要緣由是一個很大的工程中函數和全局變量的名字太多了,並且一個大工程不是一我的完成的,是不少人協助完成的,因此很難保證不重名,解決方案?
  • (3)線代高級語言彙總完美解決了這個問題的方法是命名空間(其實就是給一個變量帶上各個級別的前綴)可是C語言不是這麼解決的
  • (4)C語言比較早碰到這個問題,當時還沒發明namespace概念,當時C語言發明了一種不是很完美的可是湊合能用的解決方案,就是三種連接屬性的方法。
  • (5)C語言的連接屬性解決重名問題的思路是這樣的,將明顯不會再其餘的C文件中引用的(只有在當前C文件中引用)的函數/全局變量,使用static修飾使其成爲內連接屬性,這樣即便2個C文件中有重名的函數/全局變量,只要其中一個或者兩個爲內連接屬性就沒事。
  • (6)這種方法在必定程度上解決了問題,可是沒有從根本上解決問題,留下了不少麻煩。因此這個就致使了C語言寫很大型的項目難度很大。

4.7.7.五、static的第二種方法:修飾全局變量和函數

  • (1)普通的(非靜態的)函數/全局變量,默認的連接屬性是外連接
  • (2)static(靜態的)函數/全局變量,連接屬性是內部連接

4.7.7.六、通常用法總結:

思考:爲何static一個管家你這能夠有兩種不一樣的意思?函數

    由於這兩種用法是互斥的。就是說static一個是修飾全局變量,一個是修飾局部變量,因此不會引發歧義。優化

 4.7.八、最後的總結

  • (1)普通(自動)局部變量分配在棧上,做用域位代碼塊,生命週期是臨時的,連接屬性無連接。定義時若是未顯示初始化則其值是隨機的。變量地址由運行時在棧上分配獲得,屢次執行時地址不必定相同,函數不能返回該類變量的地址(指針)做爲返回值。
  • (2)靜態局部變量分配在數據段/bss段(顯式初始化非0則在數據段,不然在bss段),做用域爲代碼塊做用域,生命週期爲永久,連接屬性:無連接。定義時若是未顯示初始化其值爲0,變量地址由運行時環境在加載程序是肯定,整個程序運行過程當中惟一不變;靜態局部變量其實就是做用域爲代碼塊做用域(同時連接屬性爲無連接)的全局變量。靜態局部變量能夠改成用全局變量實現(程序中儘可能避免使用全局變量,由於會破壞結構性)
  • (3)靜態全局 變量/靜態函數和普通全局變量/普通函數的區別在於:static使全局變量/函數的連接屬性由外部連接(整個程序全部文件範圍內)改成內部連接(當前c文件)。這是爲了解決重名問題(C語言沒有命名空間name space的概念,所以在程序中文件變多以後全局變量/函數的重名問題很是嚴重,將沒必要要被其餘文件引用的全局變量/函數聲明爲static能夠很大程序上改善重名問題,可是仍未完全解決)
  • (4)寫程序儘可能避免使用全局變量,尤爲是非static類型的全局變量。能肯定不會被其餘文件引用的全局變量必定要static修飾
  • (5)注意區分全局變量的定義和聲明。通常規律以下:若是定義的同時又初始化則必定會被認爲定義;若是隻是定義而沒有初始化則有可能被認爲是定義,也可能被認爲是聲明,要具體分析。若是使用extern則確定會被認爲聲明(實際上使用extern也能夠有定義,實際上加extern就是聲明這個變量爲外部連接屬性)
  • (6)全局變量應該定義在c文件中而且在頭文件中聲明,而不要定義在頭文件中(由於若是定義在頭文件中,則該頭文件被多個c文件包含時,該全局變量會重複定義)
  • (7)在b.c中引用a.c中定義的全局變量/函數有2種方法:一是在a.h中聲明該函數/全局變量,而後再b.c中#include<a.h>,二是在b.c中使用extern顯式聲明要引用的函數/全局變量。其中第一種方式比較正式
  • (8)存儲類決定生命週期,做用域決定連接屬性
  • (9)宏和inline函數的連接屬性爲無連接
相關文章
相關標籤/搜索