extern 用法,全域性變數與標頭檔案(重複定義)

清風oo發表於2019-01-15

轉自

https://www.cnblogs.com/chengmin/archive/2011/09/26/2192008.html

當你要引用一個全域性變數的時候,你就要宣告extern int a;這時候extern不能省略,因為省略了,就變成int a;這是一個定義,不是宣告。

 

用#include可以包含其他標頭檔案中變數、函式的宣告,為什麼還要extern關鍵字,如果我想引用一個全域性變數或函式a,我只要直接在原始檔中包含#include<xxx.h> (xxx.h包含了a的宣告)不就可以了麼,為什麼還要用extern呢??這個問題一直也是似是而非的困擾著我許多年了,今天上網狠狠查了一下總算小有所獲了:

標頭檔案

首先說下標頭檔案,其實標頭檔案對計算機而言沒什麼作用,她只是在預編譯時在#include的地方展開一下,沒別的意義了,其實標頭檔案主要是給別人看的。

我做過一個實驗,將標頭檔案的字尾改成xxx.txt,然後在引用該標頭檔案的地方用

#include"xxx.txt"

編譯,連結都很順利的過去了,由此可知,標頭檔案僅僅為閱讀程式碼作用,沒其他的作用了!

不管是C還是C++,你把你的函式,變數或者結構體,類啥的放在你的.c或者.cpp檔案裡。然後編譯成lib,dll,obj,.o等等,然後別人用的時候最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
但對於我們程式設計師而言,他們怎麼知道你的lib,dll...裡面到底有什麼東西?要看你的標頭檔案。你的標頭檔案就是對使用者的說明。函式,引數,各種各樣的介面的說明。
那既然是說明,那麼標頭檔案裡面放的自然就是關於函式,變數,類的“宣告”了。記著,是“宣告”,不是“定義”。
那麼,我假設大家知道宣告和定義的區別。所以,最好不要傻嘻嘻的在標頭檔案裡定義什麼東西。比如全域性變數:

#ifndef _XX_標頭檔案.H
#define _XX_標頭檔案.H
int A;
#endif

那麼,很糟糕的是,這裡的int A是個全域性變數的定義,所以如果這個標頭檔案被多次引用的話,你的A會被重複定義
顯然語法上錯了。只不過有了這個#ifndef的條件編譯,所以能保證你的標頭檔案只被引用一次,不過也許還是會岔子,但若多個c檔案包含這個標頭檔案時還是會出錯的,因為巨集名有效範圍僅限於本c原始檔,所以在這多個c檔案編譯時是不會出錯的,但在連結時就會報錯,說你多處定義了同一個變數,

Linking...
incl2.obj : error LNK2005: "int glb" (?glb@@3HA) already defined in incl1.obj
Debug/incl.exe : fatal error LNK1169: one or more multiply defined symbols found

注意!!!

extern

這個關鍵字真的比較可惡,在宣告的時候,這個extern居然可以被省略,所以會讓你搞不清楚到底是宣告還是定義,下面分變數和函式兩類來說:

(1)變數

尤其是對於變數來說。
extern int a;//宣告一個全域性變數a
int a; //定義一個全域性變數a
extern int a =0 ;//定義一個全域性變數a 並給初值。
int a =0;//定義一個全域性變數a,並給初值,
第四個 等於 第 三個,都是定義一個可以被外部使用的全域性變數,並給初值。
糊塗了吧,他們看上去可真像。但是定義只能出現在一處。也就是說,不管是int a;還是extern int a=0;還是int a=0;都只能出現一次,而那個extern int a可以出現很多次。

當你要引用一個全域性變數的時候,你就要宣告,extern int a;這時候extern不能省略,因為省略了,就變成int a;這是一個定義,不是宣告。

(2)函式
函式,函式,對於函式也一樣,也是定義和宣告,定義的時候用extern,說明這個函式是可以被外部引用的,宣告的時候用extern說明這是一個宣告。 但由於函式的定義和宣告是有區別的,定義函式要有函式體,宣告函式沒有函式體,所以函式定義和宣告時都可以將extern省略掉,反正其他檔案也是知道這個函式是在其他地方定義的,所以不加extern也行。兩者如此不同,所以省略了extern也不會有問題。
比如:
int fun(void)
{
return 0;
}
很好,我們定義了一個全域性函式
int fun(void);
我們對它做了個宣告,然後後面就可以用了
加不加extern都一樣
我們也可以把對fun的宣告 放在一個標頭檔案裡,最後變成這樣
int fun(void);//函式宣告,所以省略了extern,完整些是extern int fun(void);
int fun(void)
{
return 0;
}//一個完整的全域性函式定義,因為有函式體,extern同樣被省略了。
然後,一個客戶,一個要使用你的fun的客戶,把這個標頭檔案包含進去,ok,一個全域性的宣告。沒有問題。
但是,對應的,如果是這個客戶要使用全域性變數,那麼要extern 某某變數;不然就成了定義了。

總結下:

對變數而言,如果你想在本原始檔中使用另一個原始檔的變數,就需要在使用前用extern宣告該變數,或者在標頭檔案中用extern宣告該變數;

對函式而言,如果你想在本原始檔中使用另一個原始檔的函式,就需要在使用前用宣告該變數,宣告函式加不加extern都沒關係,所以在標頭檔案中函式可以不用加extern。

C程式採用模組化的程式設計思想,需合理地將一個很大的軟體劃分為一系列功能獨立的部分合作完成系統的需求,在模組的劃分上主要依據功能。模組由標頭檔案和實現檔案組成,對標頭檔案和實現檔案的正確使用方法是:
規則1 標頭檔案(.h)中是對於該模組介面的宣告,介面包括該模組提供給其它模組呼叫的外部函式及外部全域性變數,對這些變數和函式都需在.h中檔案中冠以extern關鍵字宣告;
規則2 模組內的函式和全域性變數需在.c檔案開頭冠以static關鍵字宣告;
規則3 永遠不要在.h檔案中定義變數;
許多程式設計師對定義變數和宣告變數混淆不清,定義變數和宣告變數的區別在於定義會產生記憶體分配的操作,是彙編階段的概念;而宣告則只是告訴包含該宣告的模組在連線階段從其它模組尋找外部函式和變數。如:
int a = 5;
#include “module1.h”
#include “module1.h”
#include “module1.h”
以上程式的結果是在模組1、2、3中都定義了整型變數a,a在不同的模組中對應不同的地址單元,這明顯不符合編寫者的本意。正確的做法是:
extern int a;
#include “module1.h”
int a = 5;
#include “module1.h”
#include “module1.h”  
這樣如果模組1、2、3操作a的話,對應的是同一片記憶體單元。


規則4 如果要用其它模組定義的變數和函式,直接包含其標頭檔案即可。
許多程式設計師喜歡這樣做,當他們要訪問其它模組定義的變數時,他們在本模組檔案開頭新增這樣的語句:
extern int externVar; 
拋棄這種做法吧,只要標頭檔案按規則1完成,某模組要訪問其它模組中定義的全域性變數時,只要包含該模組的標頭檔案即可。

共享變數宣告
就像在函式間共享變數的方式一樣,變數可以在檔案中共享。為了共享函式,要把函式的定義放在一個原始檔中,然後在需要呼叫此函式的其他檔案中放置宣告。共享變數的方法和此方式非常類似。
在此之前,不需要區別變數的宣告和它的定義。為了宣告變數i,寫成如下形式:
int i; 
這樣不僅宣告i是int型的變數,而且也對i進行了定義,從而使編譯器為i留出了空間。為了宣告沒有定義的變數i,需要在變數宣告的開始處放置關鍵字extern:
extern int i;
extern提示編譯器變數i是在程式中的其他位置定義的(大多數可能是在不同的原始檔中),因此不需要為i分配空間。
順便說一句,extern可以用於所有型別的變數。在陣列的宣告中使用extern時,可以忽略陣列的長度:
extern int a[];
因為此刻編譯器不用為陣列a分配空間,所以也就不需要知道陣列a的長度了。
為了在幾個原始檔中共享變數i,首先把變數i的定義放置在一個檔案中:
int i;
如果需要對變數i初始化,那麼可以在這裡放初始值。在編譯這個檔案時,編譯器將會為變數i分配記憶體空間,而其他檔案將包含變數i的宣告:
extern int i;
通過在每個檔案中宣告變數i,使得在這些檔案中可以訪問/或修改變數i。然而,由於關鍵字extern,使得編譯器不會在每次編譯其中某個檔案時為變數i分配額外的記憶體空間。
當在檔案中共享變數時,會面臨和共享函式時相似的挑戰:確保變數的所有宣告和變數的定義一致。
為了避免矛盾,通常把共享變數的宣告放置在標頭檔案中。需要訪問特殊變數的原始檔可以稍後包含適當的標頭檔案。此外,含有變數定義的原始檔包含每一個含有變數宣告的標頭檔案,這樣使編譯器可以檢查兩者是否匹配。

如果工程很大,標頭檔案很多,而有幾個標頭檔案又是經常要用的,那麼
1。把這些標頭檔案全部寫到一個標頭檔案裡面去,比如寫到preh.h
2。寫一個preh.c,裡面只一句話:#include "preh.h"
3。對於preh.c,在project setting裡面設定creat precompiled headers,對於其他
c檔案,設定use precompiled header file

相關文章