C++ 全局變量鏈接性、extern、static關鍵字

單定義原則、外部變量與extern

C++有“單定義原則(One Definition Rule, ODR)”, 該規則決定了任何變量都只能有一次定義。爲了實現這種需求,C++提供了兩種變量聲明。一種是定義聲明(definition declaration),或者簡稱爲定義(definition),它給變量分配存儲空間;另外一種是引用聲明(referencing declaration),或者簡稱爲聲明(declaration),它不給變量分配存儲空間,因爲它引用已有的變量。

C++中引用聲明使用的關鍵字是"extern",並且不進行初始化,否則,聲明將變成定義,編譯器會給該變量分配存儲空間。

extern主要針對具有多個源文件的項目。如果有多個源文件需要使用到相同的全局變量,則這些變量只需要在其中的某一個源文件中定義一次,其它的源文件如果想使用這些外部變量,需要在聲明的時候前面加上"extern"。

//file01.cpp
extern int cats = 20; // 對cat定義
int dogs 22; // 定義dogs
int fleas; // 定義fleas
…

//file02.cpp
//use cat and dogs from file01.cpp
extern int cats; // 無需定義,直接使用外部變量
extern int dogs;
extern int fleas;
…

從上面這段代碼中可以看出,在定義變量的時候,關鍵字extern並不是必須的,但是,如果想聲明外部變量,則extern關鍵字是必不可少的。

單定義原則(ODR)≠ 不允許存在同名變量

計算機識別變量依靠的是地址(務必記住這句話,只要是地址不一樣,即使它們的名字相同,計算機也會認爲它們是不同的變量),全局變量和靜態變量存儲在靜態區,而函數中聲明、定義的變量則存儲在棧區。不同函數之間聲明的同名自動變量是彼此獨立的。但是,對於每個“變量”,程序中僅允許定義一次,服從ODR。

此外,如果在函數的內部聲明、定義一個與全局變量同名同類型的自動變量,那麼該變量的值會覆蓋原來的全局變量。以下程序對此有一個較好的詮釋。

//external.cpp
// author: pengqi
// date: 2020/03/02
// reference: C++ Primer Plus P312
#include<iostream>
using namespace std;
// external warming
double warming = 1.7; // define warming

void update(double dt);
void local();

int main() {
   cout << "Global warming is " << warming << endl;
   update(1.1);
   cout << "Global warming is " << warming << endl;
   local();
   cout << "Global warming is " << warming << endl;
   return 0;
}

------------------
//external.cpp
// author: pengqi
// date: 2020/03/02
// reference: C++ Primer Plus P312
#include<iostream>
extern double warming; // Using the external warming

void update();
void local();

using std::cout;
void update(double dt) {
   warming = dt;
}

void local() {
    double warming = 0.5;
    cout << "Local warming is " << warming << std::endl;
    cout << "However, Global warming is " << ::warming << std::endl;
}

程序運行的輸出:

$ g++ external.cpp support.cpp -o extern_test
$ ./extern_test
Global warming is 1.7
Global warming is 1.1
Local warming is 0.5
However, Global warming is 1.1
Global warming is 1.1

 從上面程序的運行結果中可以看出,函數local同樣聲明瞭一個名爲warming的變量,這時,在函數內部,warming的值使用的是新聲明的這個,原來的全局變量被隱藏了。如果想使用原來的全局變量,需要在變量前面加上“::”, 稱之爲作用域解析運算符。這也是C++比C語言優越的一點體現。

全局變量 VS 局部變量

全局變量固然有其優勢,所有的函數都可以訪問,甚至是函數在調用的時候都不用傳遞參數。但是,過分地使用全局變量也會使得程序變得不可靠。計算經驗表明,程序越能避免對數據進行不必要的訪問,就越能保持數據的完整性。C++作爲一門面向對象的編程語言,本身就在數據隔離方面邁出了關鍵性的一步。

加static的全局變量與不加的有何區別

如果整個程序只有一個源文件,那麼在全局變量前加不加static將不會對其產生任何影響。但是如果程序由多個源文件組成,那麼static就會對該全局變量的作用域產生影響了。加上了static的全局變量,其作用域將被限定在本文件內,雖然變量可以在文件內部全局調用,但是其鏈接性將變成內部。

觀察下面一段代碼:

//file1
int error = 20;
...

---------------------

//file2
int error = 5;
void foo() {
    cout << error;
}

這種情況下,程序是編不過的,因爲它違反了單定義原則(ODR)。file1已經創建了error作爲外部變量的定義,file2又試圖定義相同的變量。但是,如果把程序改成如下形式,則可以編譯通過:

//file1
int error = 20;
...

-----------------
//file2
static int error = 5;
void foo() {
    cout << error; // user will use error defined in file2
}

const對於全局變量作用域的影響

之前講過,如果不加static修飾的話,默認全局變量的鏈接性是外部的。但是,如果加了const後,該全局變量的鏈接性就會變成內部的了。

const int fingers = 10;

 以上代碼等價於:

static const int finger = 10;

如果我們希望const常量具有外部鏈接性,就需要在定義的時候前面加上extern,如下:

extern const int finger = 10;

雖然對於普通的全局變量,加不加extern不影響。

函數的鏈接性(extern 與 static)

與C語言一樣,C++不允許在一個函數中定義另外一個函數,因此所有的函數存儲持續性都自動爲靜態的,即整個程序執行期間都一直存在。默認情況下,函數的鏈接性都是外部的,即全局函數都可以在文件之間共享。實際上,可以在函數原型前面加上extern,表明該函數是在其它文件中定義的,不過這是可選的。static關鍵字同樣適用於函數,如果一個函數被static說明符修飾,則該函數只能在該文件中使用,且static必須在函數的聲明和定義中都使用。例如:

static int private(double x);
...
static int private(double x) {
...
}

與此同時,這還意味着其它文件中可以定義同名的函數。在此文件中,靜態函數會覆蓋同名的外部定義的全局函數,正如靜態變量會覆蓋全局變量一樣。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章