C++手稿:靜態和全局變量的做用域

全局變量和靜態變量的存儲方式是同樣的,只是做用域不一樣。若是它們未初始化或初始化爲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 。

關於 全局變量的初始化順序 ,同一源文件中的全局變量初始化順序按照定義順序,不一樣源文件(編譯單元)的全局變量初始化順序並未規定。 所以軟件設計時不要依賴於其餘編譯單元的靜態變量,能夠經過單例模式來避免這一點。

相關文章
相關標籤/搜索