2009-08-03 13 views
10

W języku C++ wiem, że kompilator może inicjować statyczne obiekty w dowolnej kolejności, którą wybiera (z zastrzeżeniem kilku ograniczeń) i że ogólnie nie można wybrać ani określić statycznej kolejności inicjowania.Ustalić statyczną kolejność inicjalizacji po kompilacji?

Jednak po skompilowaniu programu kompilator musi podjąć decyzję o kolejności, w jakiej należy zainicjować te obiekty. Czy istnieje sposób określenia, z kompilowanego programu z symbolami debugowania, w jakiej kolejności statycznej zostaną nazwani konstruktorzy?

Kontekst jest następujący: Mam duży program, który nagle ulega uszkodzeniu przed głównym(), gdy jest zbudowany pod nowym łańcuchem narzędzi. Jest to albo problem ze statycznym porządkiem inicjowania, albo coś jest nie tak z jedną z ładowanych bibliotek. Jednak podczas debugowania za pomocą gdb lokalizacja awarii jest po prostu zgłaszana jako surowy adres bez żadnych symbolicznych informacji lub śledzenia wstecznego. Chciałbym zdecydować, który z tych dwóch problemów jest, umieszczając punkt przerwania na konstruktorze pierwszego zainicjowanego statycznie obiektu, ale nie wiem, jak określić, który to obiekt.

+0

Czy próbowałeś rekompilować się z flagą "-g3"? To powinno zawierać wiele symboli debugowania, z którymi możesz pracować. –

+0

Jest to łącznik, który określa ostateczne zamówienie we wszystkich jednostkach kompilacji. Wierzę, że g ++ ma pewne pragmy, które mogą pomóc w zdefiniowaniu porządku. –

+0

Odpowiedź jest wysoce zależna od platformy i udało Ci się utrzymać swoją platformę w tajemnicy. Pokaż go, a także wersję GDB, z której korzystasz. –

Odpowiedz

7

Matthew Wilson zapewnia sposób na odpowiedź na to pytanie w this section (wymagana subskrypcja Safari Books Online) z Imperfect C++. (Dobra książka, nawiasem mówiąc.) Podsumowując, tworzy on nagłówek CUTrace.h, który tworzy statyczną instancję klasy, która drukuje nazwę pliku zawierającego plik źródłowy (przy użyciu niestandardowego makra preprocesora __BASE_FILE__) po utworzeniu, a następnie zawiera CUTrace.h w każdy plik źródłowy.

Wymaga to ponownej kompilacji, ale #include "CUTrace.h" można łatwo dodać i usunąć za pomocą skryptu, więc nie powinno być zbyt trudne do skonfigurowania.

+1

Bardzo sprytny pomysł. Spróbuję tego później jeszcze dziś i prawdopodobnie zaakceptuję tę odpowiedź, jeśli zadziała. –

+1

Chociaż to nie odpowiadało dokładnie na moje pytanie jako takie, rozwiązało problem, który zainspirował to pytanie. Korzystając z tej metody, mogłem zauważyć, że problem wystąpił przed jakimkolwiek statycznym zainicjowaniem w mojej aplikacji, i modyfikując biblioteki używane do zrobienia tego samego, udało mi się ustalić, która biblioteka miała problem. Znacznie łatwiej było znaleźć błąd inicjalizacji statycznej, ponieważ ta biblioteka znacznie mniej inicjalizowała niż moja aplikacja. –

2

Czy można zainicjować zmienne atrapa w przestrzeni statycznej i umieścić punkty przerwania na tych wywołaniach funkcji?

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

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

Następnie w gdb metę break breakOnMe przed uruchomieniem programu. To powinno spowodować, że gdb zatrzyma się przed każdym statycznym inicjalizowaniem.

Myślę, że to powinno działać .. Jestem trochę zardzewiały na gdbbingu.

+0

Czy gwarantuje się, że statyczne zmienne nieobiektowe będą zawsze inicjowane przed obiektami statycznymi? –

+0

W systemie Linux typy POD zostaną zainicjowane poprzez umieszczenie ich w sekcji danych - zostaną one załadowane przed wykonaniem jakiegokolwiek kodu użytkownika. – bdonlan

+0

Ale nie można się włamać do sekcji danych, więc pytanie brzmi, czy typ POD jest inicjalizowany za pomocą wywołania funkcji (jak w przypadku break1 i break2 w przykładzie eduffy), jest to gwarantowane przed konstruktorami obiektów zostać wezwanym? –

11

W języku G ++ w systemie Linux uporządkowanie statycznego konstruktora i destruktora jest określane przez wskaźniki funkcji w sekcjach .ektory i .dtors. Zauważ, że z wystarczającą debugowanie dostępny, można rzeczywiście dostać ślad:

(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 

To z symbolami debugowania dla libc i libstdC++ zainstalowany. Jak widać, zdarzyła się tutaj awaria w konstrukcie foo :: foo() dla statycznego obiektu foo_inst.

Jeśli chcesz włamać się do procesu inicjalizacji, możesz ustawić punkt przerwania na __do_global_ctors_aux i krok po kroku przez jego demontaż, tak przypuszczam. Lub po prostu czekać na awarię, aby uzyskać śledzenie jak wyżej.

+0

Na * niektórych * platformach ta odpowiedź jest prawidłowa. Na innych platformach jest źle. Prawdopodobnie nie powinieneś zakładać, że "cały świat składa się z plików binarnych ELF". –

+0

@Russian, zaktualizowany o klauzulę "on Linux" :) – bdonlan

1

można znaleźć porządek jednostki tłumaczeniowe są zainicjowany przy użyciu szablonów, jak podkreślił to question.Wymaga to mały kawałek zmiany kodu na każdej z JT jesteś zainteresowany:

// 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 

Podstawowym założeniem jest to, że każdy TU będzie miał inny unikalny adres dummyGlobal a więc szablon będzie miał inny tworzenie w każdej TU. Inicjalizacja statycznego elementu powoduje wywołanie "showCountAndFile", które następnie wypisuje SRC_FILE (ustawioną w TU) i bieżącą wartość cnt, co spowoduje wyświetlenie zamówienia.

Można by użyć go w następujący sposób:

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

int main() 
{ 
} 
0

Faktycznie, dzięki zastosowaniu pojedynczych można kontrolować kolejność inicjalizacji obiektów globalnych/statyczne dość skutecznie w C++.

Załóżmy, że masz:

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

i odpowiedni przedmiot zdefiniowane w zakresie globalnym:

Abc abc; 

Wtedy masz klasę:

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

która również obiekt zdefiniowany w globalnym zakresie:

Def def; 

W tej sytuacji nie masz kontroli nad kolejnością inicjalizacji i jeśli def została zainicjowana jako pierwsza, to prawdopodobnie Twój program ulegnie awarii, ponieważ wywołuje metodę foo() na Abc, która jeszcze nie została zainicjowany.

Rozwiązaniem jest mieć funkcję w zakresie globalnym zrobić coś takiego:

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

a następnie Def będzie wyglądać mniej więcej tak:

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

ten sposób abc jest zawsze gwarancją zainicjalizowane przed użyciem, ponieważ stanie się to podczas pierwszego wywołania funkcji abc(). Podobnie, powinieneś zrobić to samo z Def globalnym obiektem, aby nie miał żadnych nieoczekiwanych zależności inicjalizacyjnych.

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

Jeśli trzeba ściśle kontrolować kolejność inicjalizacji oprócz po prostu że wszystko zostaje zainicjowany zanim zostanie użyty, umieścić wszystkie obiekty globalne w globalnej Singleton następująco.

struct Global 
{ 
    Abc abc; 
    Def def; 
}; 

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

I odwołują się do tych elementów, co następuje:

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

Niezależnie od tego, który z nich dostaje połączeń pierwszy, C++ zasady członek inicjalizacji zagwarantuje, że abc oraz def obiekty są inicjalizowane w kolejności ich zdefiniowane w klasie Global.

Powiązane problemy