全局變量和靜態變量的存儲方式是同樣的,只是做用域不一樣。若是它們未初始化或初始化爲0則會存儲在BSS段,若是初始化爲非0值則會存儲在DATA段,見進程的地址空間分配一文。 靜態變量的做用域是當前源文件,全局變量的做用域是整個可執行程序。 值得注意的是:python
若是在頭文件定義全局變量,在預編譯期間 #include
的頭文件會被拷貝進源文件中,編譯器是不知道頭文件的。ios
雖然全局變量是全局做用域,但須要 extern
關鍵字來聲明以經過編譯。由於C++是強類型語言,編譯時須要根據變量聲明作類型檢查。bash
C++源文件中引用外部定義的全局變量和引用外部函數是同樣的語法,經過extern
來聲明:函數
// file: a.cpp #include<iostream> extern int a; //聲明 int main() { std::cout<<a<<std::endl; return 0; } // file: b.cpp #include<iostream> int a = 2; //定義
而後分別編譯這兩個文件,連接生成 a.out
並執行它:spa
$ g++ a.cpp b.cpp $ ./a.out 2
extern
只是在當前文件中聲明有這樣一個外部變量而已,並不指定它來自哪一個外部文件。因此即便 extern
變量名錯誤當前源文件也能經過編譯,但連接會出錯。設計
由於頭文件可能會被屢次引用,在預編譯時被引用的頭文件會被直接拷貝到源文件中再進行編譯。一個常見的錯誤即是把變量定義放在頭文件中,例以下面的變量 int a
:code
// file: a.cpp #include <iostream> #include "b.h" int main() { std::cout<<a<<std::endl; return 0; } // file: b.cpp #include<iostream> #include"b.h" void f(){} // file: b.h int a = 2;
頭文件 b.h
中定義了 int a
,它被 a.cpp
和 b.cpp
同時引入。咱們將a.cpp
和 b.cpp
分別編譯是沒有問題的,而後連接時會拋出錯誤:orm
duplicate symbol _a in: /tmp/ccqpfU5e.o /tmp/ccCRi9nO.o ld: 1 duplicate symbol for architecture x86_64 collect2: error: ld returned 1 exit status
兩個 .o
文件中的 _a
名稱發生了冗餘,這是變量重定義錯誤。進程
由於聲明操做是冪等的,而屢次定義會引起重定義錯誤。因此 頭文件中不該包含任何形式的定義,只應該包含聲明 , 正確的辦法是變量定義老是在源文件中進行,而聲明放在頭文件中:內存
#include <iostream> #include "b.h" int main() { std::cout<<a<<std::endl; return 0; } // file: b.cpp #include<iostream> #include"b.h" int a = 2; //定義 // file: b.h extern int a; //聲明 extern
而後編譯連接執行都會經過,輸出 2
:
$ g++ a.cpp b.cpp $ ./a.out 2
編譯器看到 g++ a.cpp b.cpp
時會自動去查找 b.h
並進行預編譯操做,所以不須要顯式指定 b.h
。
非靜態全局變量是 外部可連接的 (external linkage),目標文件中會爲它生產一個名稱供連接器使用; 而靜態全局變量是 內部可連接的 (internal linkage),目標文件中沒有爲連接器提供名稱。所以沒法連接到其餘文件中,所以靜態變量的做用域在當前源文件(目標文件)。 雖然靜態和非靜態全局變量可能存儲在同一內存塊,但它們的做用域是不一樣的。 來看例子:
// file: a.cpp #include <iostream> extern int a; int main() { std::cout<<a<<std::endl; return 0; } // file: b.cpp static int a = 2;
而後 g++ a.cpp b.cpp
時發生連接錯:
Undefined symbols for architecture x86_64: "_a", referenced from: _main in ccPLYjyx.o ld: symbol(s) not found for architecture x86_64 collect2: error: ld returned 1 exit status
連接時未找到名稱 _a
,所以靜態變量在編譯獲得的目標文件中沒有爲連接器提供名稱。因此其餘目標文件沒法訪問該變量,靜態全局變量的做用域是當前源文件(或目標文件)。
全局變量比較特殊,初始化有兩種方式:
靜態初始化(static initialization):對於定義時給出初始化參數的全局變量,其初始化在程序加載時完成。根據是否被初始化、是否被初始化爲0會把它存儲在BSS或者DATA段中,參見進程的地址空間分配。
動態初始化(dynamic initialization):定義變量時能夠不給出初始化參數,而是在某個函數中進行全局變量初始化。
對於靜態初始化,看這個例子:
class C { public: C() { std::cout<<"init "; } }; C c; int main() { std::cout<<"main"; return 0; }
在 main()
進入以前,程序加載時動態初始化,程序輸出爲一行 init main
。
關於 全局變量的初始化順序 ,同一源文件中的全局變量初始化順序按照定義順序,不一樣源文件(編譯單元)的全局變量初始化順序並未規定。 所以軟件設計時不要依賴於其餘編譯單元的靜態變量,能夠經過單例模式來避免這一點。