C++ 靜態空間變數 - Static Storage Variable

(注意: 靜態空間變數/全域變數,是接近 evil 的東西,除非有非常好的理由,或是當下有相當清楚的意志,才去使用它。)

靜態空間變數,代表它的存活時間是直到程式結束,並且只會有一份存在。通常用來存放持續性的資料、或是避免產生複本來提升效能。以下細分成七種,是因為它們在一些可視範圍初始化順序初始化方法C++11 改進,上有些許的不同。

1. Global 變數,又稱 Namespace-Level 變數

定義在函式外部,也就是 放在一般函式、 放在main 函式、和 放在 class 定義式內部,就是一個 Global 變數。以下四種主要的差別是在可視範圍。

1.1 一般 global

可視範圍是 每個檔案 ,只要另一個檔案有宣告(declare) 使用 extern 就可以看到並使用到此變數。

1.2 static global

可視範圍是 只能這個檔案 , 就算別的檔案使用 extern宣告,也會查無此變數。如果要使用全域變數,請試著加上 static ,因為至少它能夠避免掉,汙染其他檔案的名稱空間。

(注意: 這裡的 static 語意不是指 storage 而是指 scope)

1.3 const global

可視範圍是 只能這個檔案,恰恰跟 一般 global 相反。

1.4 extern const global

可視範圍是 每個檔案,需要加上 extern 來顯性宣告。

(注意: 這裡的 extern 語意是被別人看到,跟一般用來宣告使用的 extern 想看到別人不一樣。)

Cat.cpp
int GlobalCat = 10;
static int StaticCat = 20;
static const int GlobalConstCat = 30;
extern const int ExternConstCat = 40;

void someOtherFunction()
{
GlobalCat = 100; // ok - 每個函式內部都看得見。
StaticCat = 200; // ok
int GlobalCat = 1000;// compile-ok - 很好,歡迎進入 Buggy Code 的大門
// 目前有兩份 GlobalCat ,一個是放在 statck 的 auto 變數
// 另一個是早先定義的 Global 變數,越晚定義的先被看到。
...
int expectedMeet10Cat = GlobalCat; // 你很容易誤用你想要用的 **那一份** `GlobalCat`。
};
...
main.cpp
extern int GlobalCat; 
// compile-ok link-ok ,的確,GlobalCat 有在另一份 Cat.cpp 擁有外部連結性,所以找的到。

extern int StaticCat; 
// compile-ok, link-error 。Static Global 限制內部連結性,外面的程式無法找到 Static Global。

extern int GlobalConstCat; // edit: 感謝 kkthegamer 指出筆誤
// compile-ok, link-error 。 const static global 預設是 只有內部連結性,所以 linker 會找不到。

extern int ExternConstCat; 
// compile-ok, link-ok 。在強制宣告 ExternConstCat 這個 const staic global 為 extern ,使它有外部連結性。
int main()
{
  int GlobalCat = 10000; // 依然會是走向 buggy code 的歡樂世界。
}

2. Local Static 變數

2.1 local static

定義在函式內部,可視範圍當然就是跟著函式。程式在離開函式時,變數的生命週期不會結束。
不像 auto 變數,是存放在 stack 裡,一但離開函式,就結束變數生命。

void function()
{
  static int staticCallTimes = 0; // 每一次進來 function() ,都是同一份變數。
  staticCallTimes++;
  int autoCallTimes = 0; // 每一次進來 function() ,都是全新的一份變數。
  autoCallTimes++;
  cout << "static: " << staticCallTimes << ", auto: " << autoCallTimes << endl;
  return; // autoCallTimes 會在 return 完,結束生命。
}
int main()
{
  for(int i = 0; i < 3; i++)
    function();
  return 0;
}
Output
static: 1, auto: 1
static: 2, auto: 1
static: 3, auto: 1

解決 Global 變數無法保證初始化順序的問題

PresidentPassword.cpp
longlong PresidentPassword = 0xA3345678LL;
NuclearMissile.cpp
extern longlong PresidentPassword;
if( ! MissileInitiated(PresidentPassword))
    explodedRightNow(); // 你無法知道,你的變數是否已被初始化,走向 Buggy Code 的世界。

Effective C++ Rule 4 有提到, Global 變數最大的缺點之一就是你無法確認你使用的 Global 變數,到底初始化了沒有,也就會 C++ 沒有定義 Global 變數的初始化順序,依靠這個就會導致 Undefined Behavior

這個時救星就來了,C++ 有保證 Local Static 變數,只要 Call 了函式,就會初始化函式內部的 Local Static 變數,所以當你把一個 static 變數用函式包裝好後,一定保證使用到它會是一個被初始化的值,非常安全。

SmarterPresidentPassword.cpp
longlong& PresidentPassword(){ // 注意這裡要記得回傳 reference,否則就會是 call by value 複製一份了。
  static longlong ObamaSays = 0xA3345678LL;
  return ObamaSays;
}
NuclearMissile.cpp
if( ! MissileInitiated(PresidentPassword())) // 從呼叫變數變成經由呼叫函式,來取得 static 變數。
 explodedRightNow(); // 你現在可以確保,使用的 static 變數,是已經被初始化過的。

3. Class Static 變數

就是宣告在 Class 宣告式內部的 static 變數,可視範圍是跟著 Class,可以使用 CLASS::VARIABLE 來存取,只存在一份,所以有時會用來記錄一些跟 Class level 相關的資訊。

3.1 class static

在 C++03 時代,class static 變數不能在 class 宣告式中被初始化,C++11 可以,所以你要在宣告好 Static 變數後,在實作檔中 定義 它。

(注意: 在這裡是指定義,也就是要再寫一次變數型別,就跟定義其他變數一樣,而不是簡單的賦值。)

3.2 const class static

有了 const 修飾詞後,C++ 特別允許你在 Class 中宣告時給予初值。 C++ Primer 有說買宣告+買初始化送定義,所以不需要在 Class 外部再次定義它。 (In C++03 ok)。
有時使用 enum 會更加好用,《Modern C++ Design》 中的 Metaprogramming ,就大量依靠 class enum 來基礎。

Class Static 變數
class Cat
{public:
  static int non_const_variable_01;
  // 宣告式,你不能賦值給一個未定義的變數。
  
  static int non_const_variable_02;
  // 宣告式,準備在 class 外部定義。
  
  static int non_const_variable_03 = 0; 
  // compile error: ISO C++ forbids in-class initialization of non-const static member 'Cat::non_const_variable_03'
  // C++03 禁止你在 class 內部初始化一個 non-const static 變數。
  
  static const int const_variable_01 = 9; // compile OK
  // C++03/11 都允許你宣告並定義一個 const class static。
 };
 
 Cat::non_const_variable_01 = 3; // compile-error
 // 變數還未定義。
 
 int Cat::non_const_variable_02 = 3; 
 // 像定義式一樣去定義它。
 
 int main()
{
   cout << Cat::non_const_variable_01 << endl; // compile error: 
   // undefined reference to `Cat::non_const_variable_01'
   // 因為 non_const_variable_01 只是被宣告,還沒有被定義。
 
    cout << Cat::non_const_variable_02  << endl; 
    // ok ,此變數已被定義。
 
   cout << const_variable_01  << endl; 
   // ok ,此變數已被定義。
   return 0;
 }
comments powered by Disqus