對編譯器來說,靜態成員變量的初始化順序和析構順序是一個未定義的行為
#include <string>
#include <iostream>
using namespace std;
class Base{
public:static int b;static int a;};
int Base::b = 2;
int Base::a = b + 1;int main()
{Base base;cout <<"Base::a="<< Base::a << endl;cout <<"Base::b="<< Base::b << endl;return 0;
}
6 7 18 19 行怎么修改值都一樣可見靜態類型成員變量的初始化順序和編譯器和有關
如下文所說:https://wenku.baidu.com/view/60a101842b4ac850ad02de80d4d8d15abe2300da.html
理解在?個應?程序內部,靜態變量的構造/析構順序。其中,對于全局靜態變量,視編譯器的實現?定(?種?式是根據字
母順序來決定);對于有依賴關系的,那么視依賴關系?定。?對于局部靜態變量來說,問題就開始復雜了。這也正是本?論述的重點。
?先,要理解編譯器是如何實現局部靜態變量的語法特性的。從語法上來看,局部靜態變量與全局靜態變量最?的不同在于構造時機——當
且僅當程序執?路徑?次達到局部靜態變量的定義處才出發構造。注意是?次。印象中編譯器是通過添加?個標識變量(當然這個變量?定
是全局靜態的)來實現的(即每次程序執?到時,?先檢查這個標志變量),如此來確保調?時構造且只構造?次的特性。那么反過來看局
部靜態變量的析構,編譯器會維護?個析構函數的函數指針棧,?旦構造完成,就會把相應的析構函數指針放到這個棧中。當程序結束后,
由編譯器?成的doexit函數會逐個調?這些析構函數,完成進程結束前的掃尾?作。基于此,很顯然,對于分布在程序各處的靜態局部變
量,其構造順序取決于它們在程序的實際執?路徑上的先后順序,?析構順序則正好與之相反。
很簡單,不是么?可為什么說是隱藏的坑呢,問題在于:
???是因為程序的實際執?路徑有多個決定因素(例如基于消息驅動模型的程序和多線程程序),有時是不可預知的; 另???是因為局部靜態變量分布在程序代碼各處,彼此直接沒有明顯的關聯,很容易讓開發者忽略它們之間的這種關系(這是最坑的地 ?)。
既然提出問題,那么就討論應對之道:
(1)最簡單的,避免使?局部靜態變量,將變量的聲明周期控制在開發者?中;
(2)如果確有需要,那么盡量確保局部靜態變量之間構造和析構是彼此獨?互不相關的,換句話說,它們可以以任意的順序被構造和析
構;
設計模式里的單例模式就有靜態變量互相引用從而系統奔潰。
測試程序
#include <string>
#include <iostream>
using namespace std;
class Log
{
public:static Log* GetInstance(){static Log oLog;return &oLog;}void Output(string strLog){cout<<strLog<<(*m_pInt)<<endl;}
private:Log():m_pInt(new int(3)){}~Log(){cout<<"~Log"<<endl;delete m_pInt;m_pInt = NULL;}int* m_pInt;
};class Context
{
public:static Context* GetInstance(){static Context oContext;return &oContext;}~Context(){Log::GetInstance()->Output(__FUNCTION__);}void fun(){Log::GetInstance()->Output(__FUNCTION__);}
private:Context(){}Context(const Context& context);
};int main(int argc, char* argv[])
{Context::GetInstance()->fun();return 0;
}
參考博客:https://www.freesion.com/article/7937607333/