2009-08-03 39 views
10

在C++中,我知道編譯器可以選擇以任何順序初始化它所選擇的靜態對象(受到一些約束),並且通常您不能選擇或確定靜態初始化順序。確定編譯後的靜態初始化順序?

但是,一旦程序編譯完成後,編譯器必須決定初始化這些對象的順序。有沒有什麼方法可以通過帶有調試符號的已編譯程序來確定靜態構造函數會被調用?

上下文是這樣的:我有一個相當大的程序,它在main()之前突然被切斷,當它被構建在一個新的工具鏈下時。這是一個靜態的初始化順序問題,或者它正在加載的一個庫有問題。但是,當我使用gdb進行調試時,崩潰位置僅作爲原始地址報告,沒有任何符號信息或回溯。我想通過在第一個靜態初始化對象的構造函數處放置一個斷點來決定這兩個問題中的哪一個,但是我不知道該如何分辨哪個對象。

+0

你有沒有嘗試用「-g3」標誌重新編譯?這應該爲你提供大量的調試符號。 – 2009-08-03 20:35:05

+0

它是確定所有編譯單元的最終排序的鏈接器。我相信g ++有一些可能有助於定義順序的編譯指示。 – 2009-08-03 21:03:47

+0

答案具有很高的平臺特定性,並且您已經設法保護您的平臺。請透露它,以及您使用的GDB版本。 – 2009-08-04 04:40:02

回答

7

Matthew Wilson在this section(需要Safari Books Online訂閱)中提供了一種方法來回答這個問題Imperfect C++。 (本好書,順便說一句。)總之,他創建了一個CUTrace.h頭創建一個打印包括源文件的文件名(使用非標準的預處理宏__BASE_FILE__)創建的時候,那麼他包括CUTrace.h一類的靜態實例每個源文件。

這需要重新編譯,但中的#include「CUTrace.h」可以很容易地通過一個腳本添加和刪除,所以它應該不會太難成立。

2

你能初始化靜態空間虛擬變量,並把這些函數調用突破點?

extern "C" int breakOnMe() { return 0 }; 

int break1 = breakOnMe(); 
float pi = 3.1415; 
int break2 = breakOnMe(); 
myClass x = myClass (1, 2, 3); 

然後在gdb運行break breakOnMe在執行程序之前。這應該讓gdb在靜態初始化之前暫停。

我認爲應該工作..我在gdbbing有些生疏。

11

在G ++在Linux,靜態構造和析構排序由在.ctors和.dtors段函數指針確定。需要注意的是有足夠的調試用,其實你可以得到一個回溯:

(gdb) bt 
#0 0xb7fe3402 in __kernel_vsyscall() 
#1 0xb7d59680 in *__GI_raise (sig=6) 
    at ../nptl/sysdeps/unix/sysv/linux/raise.c:64 
#2 0xb7d5cd68 in *__GI_abort() at abort.c:88 
#3 0x08048477 in foo::foo()() 
#4 0x0804844e in __static_initialization_and_destruction_0(int, int)() 
#5 0x0804846a in global constructors keyed to foo_inst() 
#6 0x0804850d in __do_global_ctors_aux() 
#7 0x08048318 in _init() 
#8 0x080484a9 in __libc_csu_init() 
#9 0xb7d4470c in __libc_start_main (main=0x8048414 <main>, argc=1, 
    ubp_av=0xbfffcbc4, init=0x8048490 <__libc_csu_init>, 
    fini=0x8048480 <__libc_csu_fini>, rtld_fini=0xb7ff2820 <_dl_fini>, 
    stack_end=0xbfffcbbc) at libc-start.c:181 
#10 0x08048381 in _start() at ../sysdeps/i386/elf/start.S:119 

這與libc中和libstdC++安裝調試符號。正如你所看到的,這裏的崩潰發生在foo :: foo()構造函數中。

如果你想打入初始化過程,然後你可以設置__do_global_ctors_aux斷點,並通過其拆卸步驟,我想。或者等待它崩潰以獲得像上面那樣的回溯。

1

你可以找到的TU正在使用模板本question所強調初始化的順序。它需要代碼更改到每個TU的是你有興趣在一個小位:

// order.h 
// 

#ifndef INCLUDED_ORDER 
#define INCLUDED_ORDER 

#include <iostream> 

inline int showCountAndFile (const char * file) 
{ 
    static int cnt = 0; 
    std::cout << file << ": " << cnt << std::endl; 
    ++cnt; 
    return cnt; 
} 

template <int & i> 
class A { 
    static int j; 
}; 

template <int & i> 
int A<i>::j = showCountAndFile (SRC_FILE); 

namespace 
{ 
    int dummyGlobal; 
} 
template class A<dummyGlobal>; 

#endif 

的基本思想是每個TU都會有不同的唯一地址dummyGlobal等的模板將有不同在每個TU中實例化。靜態成員的初始化導致對「showCountAndFile」的調用,然後打印出SRC_FILE(在TU中設置)和當前值cnt,因此將顯示訂單。

如下你會使用它:

static const char * SRC_FILE=__FILE__; 
#include "order.h" 

int main() 
{ 
} 
0

事實上,通過利用單身的你可以非常有效地在空調控制全局/靜態對象的初始化順序++。

例如,假設您有:

class Abc 
{ 
public: 
    void foo(); 
}; 

,並在全球範圍內定義的相應的對象:

Abc abc; 

然後你有一個類:

class Def 
{ 
public: 
    Def() 
    { 
     abc.foo(); 
    } 
}; 

其中也有在全球範圍內定義的對象:

Def def; 

在這種情況下,您無法控制初始化順序,並且如果首先初始化def,那麼很可能您的程序將崩潰,因爲它正在調用abc上的foo()方法,該方法尚未初始化。

的解決方案是在全局範圍內的功能做這樣的事情:

Abc& abc() 
{ 
    static Abc a; 
    return a; 
} 

,然後防守看起來是這樣的:

class Def 
{ 
public: 
    Def() 
    { 
     abc().foo(); 
    } 
}; 

這樣,農行始終保證是在使用它之前初始化,因爲這將在首次調用abc()函數期間發生。同樣,你應該對Def全局對象做同樣的事情,以使它不會有任何意外的初始化依賴。

Def& def() 
{ 
    static Def d; 
    return d; 
} 

如果您需要嚴格控制,除了簡單地確保一切初始化的順序初始化在使用之前,把所有的全局對象在全局單如下。

struct Global 
{ 
    Abc abc; 
    Def def; 
}; 

Global& global() 
{ 
    static Global g; 
    return g; 
} 

做出這些項目的引用如下:

//..some code 
global().abc.foo(); 
//..more code here 
global().def.bar(); 

無論哪一個首先得到調用,C++成員初始化規則將保證ABC和DEF對象在他們的順序初始化在Global類中定義。