這兩天項目代碼中遇到一個很疑惑的問題,問題可以描述為: 一個靜態成員初始化的時候直接core 掉,該靜態成員初始化時通過另外一個文件中靜態成員來完成 。該問題同樣發生在全局對象上。該問題可以描述為今天要討論的: 變量的靜態初始化順序 。
具體可以用代碼簡述如下:
//test1.cpp
#include <string>
std :: string a = "test";
//test2.cpp
#include <iostream>
extern std::string a;
std::string b = a;
int main()
{
std::cout<<b<<std::endl;
}
當執行如下編譯命令:
g++ -g test1.cpp test2.cpp
執行結果正確,輸出”test”文本,但當執行如下編譯指令:
g++ -g test2.cpp test1.cpp
執行結果如下:
Segmentation fault (core dumped)
調試 core 文件,函數幀棧如下:
(gdb) bt
#0 0x00007ff5f0932f2b in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::string const&) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1 0x0000000000400af8 in __static_initialization_and_destruction_0 (__initialize_p=1,
__priority=65535) at test4.cpp:7
#2 0x0000000000400b24 in _GLOBAL__sub_I_b () at test4.cpp:12
#3 0x0000000000400c2d in __libc_csu_init ()
#4 0x00007ff5f02de700 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#5 0x00000000004009c9 in _start ()
可以看到程序在靜態初始化全局對象時,調用 string 的 copy constructor 導致內存訪問異常。該問題的原因就是 依賴的靜態成員還沒有進行初始化導致的 。
我們知道對於程序的所有全局和靜態數據成員,都是放在全局數據區。對於已經初始化的全局和靜態變量時存放在可執行文件的數據段( .data ),而對於未初始化的全局和靜態變量,則在 BSS 段中( BSS 段在生成的可執行文件中並不存在,直到程序被加載到內存中),程序被加載到內存后, BSS 段的內存被清零。
這里首先強調一個概念: 靜態初始化:靜態對象(包括全局和靜態變量)的初始化 。
在程序加載到內存后,對於存在數據段中的全局和靜態變量, dynamic linker loader 在 程序員 指定的 動態初始化 發生前,會保證每一個靜態對象初始化為零( 當然,這里只針對內置數據類型 )。而對於自定義數據類型,例如程序中的 string 對象,如果 a 未被先動態初始化,那么 a 的內存空間的數據就是未定義,就會出現調用 string 的 copy constructor
導致內存訪問異常。這種情況很容易在跨文件引用時出現。
在《 C++ 編程思想》 p245
中針對靜態初始化依賴問題給出了一下幾點建議:
- 避免靜態對象初始化的依賴;
- 把靜態對象放到同一個編譯單元中,即同一文件。
- 如果一定要把靜態對象放到不同的編譯單元中,可使用兩種程序設計技術進行解決
這里只說一下里面提到的技術二: 通過函數獲取靜態對象 。
其實我們始終關心的 對象的初始化順序,而不是初始化時間 。為了解決這個問題,這種技術采用:把一個靜態對象放到一個返回該對象引用的的函數中,訪問該靜態對象的唯一途徑就是通過該函數,而在函數第一次被調用時,就會強迫該靜態對象進行初始化。該技術依賴的特性是: 函數內部的靜態對象在函數第一次被調用時進行初始化,且在程序生命周期只被初始化一次 。這樣靜態對象的初始化順序就是由設計的代碼而不是鏈接器的鏈接順序來決定。上述的代碼通過該技術,可以更改為為下面:
//test1.cpp
#include <string>
using namespace std;
const string & GetA()
{
static string a = "test";
return a;
}
//test2.cpp
#include <string>
#include <iostream>
using namespace std;
const string &GetA();
string b = GetA();
int main()
{
cout<<b<<endl;
}
上面的代碼就不會存在一開始出現的那種問題。
轉載:http://blog.csdn.net/anonymalias/article/details/38473985?utm_source=tuicool&utm_medium=referral