1. 程式人生 > >C++中static extern關鍵字及全域性變數的辨析

C++中static extern關鍵字及全域性變數的辨析

全域性變數,就是指那些定義在函式或類之外的變數,它可作為多個類或函式都需要共同使用的變數。
(1)全域性變數會被自動初始化,函式中的變數不會被自動初始化,類中定義的那些成員變數(內建)也不會自動初始化。那麼這裡有一個疑問,為什麼要這樣設定?並且為什麼,程序記憶體區中,分為初始化了的全域性變數和靜態變數,和未初始化的全域性和靜態變數。
(2)全域性變數如果加上static關鍵字,事情將會變得很奇妙。
我們知道,全域性變數理論上是指應用程式級別的全域性。普通的全域性變數是面向整個程式的,當各個檔案各自編譯,然後連結成一個可執行程式之後,全域性變數的確是被所有檔案中的“看到的”,但是,如果想讓別的檔案在編譯程式的時候就能夠看到,(如果你不做然後措施,直接使用另外一個檔案中定義的變數,那將通不過編譯)
有兩個辦法:
第一個就是#include方法

,但是這個方法,其實是將檔案合併成一個檔案,並沒有正面回答。
第二個就是在要使用該變數的檔案中宣告(extern)一個外部變數,這樣等於就是告訴編譯器,“該變數是有的,只是在另外一個檔案中,等到連結的時候你就可以看到了。”這個extern必須要加,否則就是重新定義個變數,到時候連線會出現重定義問題。
但是,如果你在全域性變數前加上static,那麼它的作用範圍就變小了,程式設計了檔案範圍。這就會導致該變數是不容許別的檔案通過extern宣告方式來操作。因為該變數對其它檔案是不可見的。
我們所說的變數的生命週期和作用域時我們有這樣的說法:
1) 全域性變數和靜態變數的生命週期為整個程序,他們都處在記憶體的同一個區域
2) 普通全域性變數的作用域為全域性(整個軟體,可跨域檔案,其他檔案想使用可以使用extern宣告),函式中的靜態變數的作用域為該函式。
3) 靜態全域性變數,的作用域則被限制在該檔案中,所以,這個時候其他檔案通過extern想使用該檔案是不行的,(就算成功啦,那是因為你引用了其他檔案中的定義)。同時,由於作用域被限制在當前檔案,所以,不同的檔案定義自己的全域性變數就不會擔心與其他檔案出現衝突。

一直以來,有一個有關全域性變數定義的標準:
1) 如果在這個檔案中定義的全域性變數不打算給別人用,那麼你就將它定義為static全域性變數吧!因為這樣你不必擔心其他檔案也定義了一個同名變數,在連線的時候出現重定義。
2) 如果你的全域性變數是打算給其他檔案使用的,那麼就不要加上static,因為這樣在其他檔案中可以使用extern對該定義進行引用。
3) 這麼說來,static 和extern是不能同時用來修飾一個變數的,extern修飾表示該變數只是宣告,宣告它使用了其他檔案的變數定義,static的修飾表示我這個變數(自己定義的),只能被當前檔案訪問。兩者完全衝突,所以編譯器會報錯——‘n’的宣告中有相互衝突的限定符。

函式或變數在宣告時,並沒有給它實際的實體記憶體空間,它有時候可以保證你的程式編譯通過, 但是當函式或變數定義的時候,它就在記憶體中有了實際的物理空間,如果你在編譯模組中引用的外部變數沒有在整個工程中任何一個地方定義的話, 那麼即使它在編譯時可以通過,在連線時也會報錯,因為程式在記憶體中找不到這個變數!你也可以這樣理解, 對同一個變數或函式的宣告可以有多次,而定義只能有一次!

在IDE開發工具大行其道的今天,對於編譯的一些概念很多人已經不再清楚了,很多程式設計師最怕的就是處理連線錯誤(LINK ERROR), 因為它不像編譯錯誤那樣可以給出你程式錯誤的具體位置,你常常對這種錯誤感到懊惱,但是如果你經常使用gcc,makefile等工具在linux或者嵌 入式下做開發工作的話,那麼你可能非常的理解編譯與連線的區別!當在VC這樣的開發工具上編寫完程式碼,點選編譯按鈕準備生成exe檔案時,VC其實做了兩 步工作,第一步,將每個.cpp(.c)和相應.h檔案編譯成obj檔案;第二步,將工程中所有的obj檔案進行LINK生成最終的.exe檔案,那麼錯 誤就有可能在兩個地方產生,一個是編譯時的錯誤,這個主要是語法錯誤,另一個是連線錯誤,主要是重複定義變數等。我們所說的編譯單元就是指在編譯階段生成 的每個obj檔案,一個obj檔案就是一個編譯單元,也就是說一個cpp(.c)和它相應的.h檔案共同組成了一個編譯單元,一個工程由很多個編譯單元組 成,每個obj檔案裡包含了變數儲存的相對地址等 。

Extern有兩種用法:

(1)C一起使用extern C,來在C++中表達,在編譯的時候按照C的風格進行編譯。

(2)於是普通修飾全域性變數的宣告,externint n;)注意它只能是宣告,不能定義初始化,它宣告表示,該變數可以從另外一個檔案中找到,現在大膽使用就行了。到時候(連結的時候)就可以找到了,所以我認為這個extern關鍵是來支援,當前檔案可以通過編譯器的檢查。當前檔案使用了另外一個檔案內定義的東西,但是有不能直接#include它,所以這導致這種技術的出現。注意,必須有一個檔案定義了該變數(int n;)如果沒有一個檔案定義了它,那麼編譯的時候能夠通過,但是在連線的時候,檔案找不到你給它的承諾,所以就會報錯——undefined reference to `n'
    當extern不與"C"在一起修飾變數或函式時,如在標頭檔案中: extern int g_Int; 它的作用就是宣告函式或全域性變數的作用範圍的關鍵字,其宣告的函式和變數可以在本模組或者其他模組中使用,記住它是一個宣告不是定義!也就是說B模組(編譯 單元)要是引用模組(編譯單元)A中定義的全域性變數或函式時,它只要包含A模組的標頭檔案即可, 在編譯階段,模組B雖然找不到該函式或變數,但它不會報錯,它會在連線時從模組A生成的目的碼中找到此函式。

Static的應用場景:

(1)C++面向物件中修飾成員函式或變數,這表明它所屬範圍為這個類,而不是物件。

其實,這個時候,我們完全可以,將他理解為,使用範圍限定在類中的,全域性變數.(其實完全就是這樣,靜態變數和全域性變數,其實是聯絡很緊密的,在記憶體中,他們儲存的位置是一樣的,生命週期也是一樣的,關鍵在於他們的可視範圍發生的變化而已。

(2)在區域性變數中(函式中)修飾變數,表示該變數是這個函式範圍內的不變的,而不是隨著每一次的函式被呼叫而更新。所以你會發現,static修飾函式中的變數和修飾類中的成員有異曲同工之妙。函式每次被呼叫,類似於類每次被例項化。普通的成員都會是一個新的,而static成員與至始至終屬於該函式(類)。

(3)在全域性變數中修飾變數,表示該變數的作用範圍為當前檔案,而不是整個程式。

static全域性變數與普通的全域性變數有什麼區別 ? static區域性變數和普通區域性變數有什麼區別?static函式與普通函式有什麼區別?

  全域性變數(外部變數)的說明之前再冠以static就構成了靜態的全域性變數。全域性變數本身就是靜態儲存方式,靜態全域性變數當然也是靜態儲存方式。這兩者在儲存方式上並無不同。這兩者的區別雖在於非靜態全域性變數的作用域是整個源程式當一個源程式由多個原始檔組成時,非靜態的全域性變數在各個原始檔中都是有效的。而靜態全域性變數則限制了其作用域,即只在定義該變數的原始檔內有效,在同一源程式的其它原始檔中不能使用它。由於靜態全域性變數的作用域侷限於一個原始檔內,只 能為該原始檔內的函式公用,因此可以避免在其它原始檔中引起錯誤。

  從以上分析可以看出,區域性變數改變為靜態變數後是改變了它的儲存方式即改變了它的生存期。把全域性變數改變為靜態變數後是改變了它的作用域, 限制了它的使用範圍

static函式與普通函式作用域不同。僅在本檔案。只在當前原始檔中使用的函式應該說明為內部函式(static),內部函式應該在當前原始檔中說明和定義。對於可在當前原始檔以外使用的函式,應該在一個頭檔案中說明,要使用這些函式的原始檔要包含這個標頭檔案

static全域性變數與普通的全域性變數有什麼區別:static全域性變數只初使化一次,防止在其他檔案單元中被引用;

static區域性變數和普通區域性變數有什麼區別:static區域性變數只被初始化一次,下一次依據上一次結果值;

static函式與普通函式有什麼區別:static函式在記憶體中只有一份,普通函式在每個被呼叫中維持一份拷貝