2009-08-03 18 views
10

En C++, sé que el compilador puede elegir inicializar objetos estáticos en el orden que elija (sujeto a algunas restricciones) y que, en general, no puede elegir o determinar el orden de inicialización estático.¿Determina el orden de inicialización estático después de la compilación?

Sin embargo, una vez que se compila un programa, el compilador debe haber tomado una decisión sobre el orden en que se inicializarán estos objetos. ¿Hay alguna manera de determinar, a partir de un programa compilado con símbolos de depuración, en qué orden estática? Se llamarán constructores?

El contexto es el siguiente: Tengo un programa considerable que de repente se segfaulting antes de main() cuando está construido bajo una nueva cadena de herramientas. O bien, este es un problema de orden de inicialización estática, o es algo incorrecto con una de las bibliotecas que está cargando. Sin embargo, cuando depuro con gdb, la ubicación del bloqueo simplemente se informa como una dirección sin procesar, sin ninguna información simbólica o traza inversa. Me gustaría decidir cuál de estos dos problemas es colocando un punto de interrupción en el constructor del primer objeto estáticamente inicializado, pero no sé cómo decir qué objeto es ese.

+0

¿Ha intentado recompilar con la bandera "-g3"? Eso debería incluir muchos símbolos de depuración para que trabajes. –

+0

Es el enlazador que determina el orden final en todas las unidades de compilación. Creo que g ++ tiene algunos pragmas que pueden ayudar a definir el orden. –

+0

La respuesta es altamente específica de la plataforma y ha logrado mantener su plataforma en secreto. Por favor, muéstralo, así como la versión de GDB que has usado. –

Respuesta

7

Matthew Wilson proporciona una manera de responder esta pregunta en this section (se requiere suscripción a Safari Books Online) de Imperfect C++. (Buen libro, por cierto). Para resumir, crea un encabezado CUTrace.h que crea una instancia estática de una clase que imprime el nombre del archivo fuente incluido (usando la macro del preprocesador no estándar __BASE_FILE__) cuando se crea, luego incluye CUTrace.h en cada archivo fuente.

Esto requiere una recompilación, pero el #include "CUTrace.h" se puede agregar y eliminar fácilmente a través de un script, por lo que no debería ser demasiado difícil de configurar.

+1

idea muy inteligente. Lo intentaré más tarde hoy, y probablemente acepte esta respuesta si funciona. –

+1

Si bien esto no respondía exactamente mi pregunta per se, sí resolvió el problema que inspiró la pregunta. Usando este método, pude ver que el problema ocurría antes de cualquier inicialización estática en mi aplicación, y al modificar las bibliotecas que solía hacer lo mismo, pude determinar qué biblioteca tenía el problema. Fue mucho más fácil encontrar el error de inicialización estático ya que esa biblioteca realizó una inicialización mucho menos estática que mi aplicación. –

2

¿Podría inicializar las variables ficticias en el espacio estático y poner punto de interrupción en esas llamadas a funciones?

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

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

Luego, en gdb plazo break breakOnMe antes de ejecutar el programa. Eso debería hacer que gdb se detenga antes de cada una de las inicializaciones estáticas.

Creo que debería funcionar ... Estoy un poco oxidado en gdbbing.

+0

¿Se garantiza que las variables estáticas sin objeto siempre se inicializarán antes que los objetos estáticos? –

+0

En Linux, los tipos de POD se inicializarán colocándolos en la sección de datos; estos se cargarán antes de que se ejecute cualquier código de usuario. – bdonlan

+0

Pero no se puede dividir en la sección de datos, por lo que la pregunta es si un tipo POD se inicializa mediante una llamada a una función (como con break1 y break2 en el ejemplo de eduffy), eso está garantizado antes que los constructores de objetos ser llamado? –

11

En G ++ en Linux, el constructor estático y el ordenamiento de destructor están determinados por los punteros de función en las secciones .ctors y .dtors. Tenga en cuenta que con la suficiente depuración disponibles, en realidad se puede conseguir una traza:

(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 

Esto es con símbolos de depuración para libc y libstdC++. Como puede ver, el bloqueo aquí se produjo en el constructor foo :: foo() para el objeto estático foo_inst.

Si quiere entrar en el proceso de inicialización, podría establecer un punto de interrupción en __do_global_ctors_aux y pasar por su desmontaje, supongo. O simplemente espera a que se bloquee para obtener la traza inversa como la anterior.

+0

En * algunas * plataformas esta respuesta es correcta. En otras plataformas, está mal. Probablemente no deberías asumir que "todo el mundo está hecho de binarios ELF". –

+0

@Russian, actualizado con un descargo de responsabilidad "sobre Linux" :) – bdonlan

1

puede encontrar el orden de las UT se está inicializando el uso de plantillas como se destaca en este question.Se requiere un poco de cambio de código a cada una de las unidades de formación que le interesa:

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

La idea básica es que cada TU tendrá una única dirección diferente para dummyGlobal por lo que la plantilla tendrá un diferente instanciación en cada TU. La inicialización del miembro estático da como resultado la llamada a "showCountAndFile", que imprime SRC_FILE (establecido en la TU) y el valor actual de cnt, que mostrará el orden.

tendrá que utilizar la siguiente manera:

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

int main() 
{ 
} 
0

En realidad, a través del uso de Singleton se puede controlar el orden de inicialización de objetos globales/estática con bastante eficacia en C++.

Por ejemplo, supongamos que tiene:

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

y un objeto correspondiente definido en el ámbito global:

Abc abc; 

Entonces usted tiene una clase:

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

que también tiene un objeto definido en el alcance global:

En esta situación, no tiene control de la orden de inicialización y si def se inicializa primero, entonces es probable que su programa falle debido a que está llamando al método foo() en un Abc que aún no ha sido inicializado

La solución es tener una función en el ámbito global hacer algo como esto:

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

y luego Def sería algo como:

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

De esta manera, abc siempre se garantiza que sea inicializado antes de su uso porque esto sucederá durante la primera llamada de la función abc(). Del mismo modo, debe hacer lo mismo con el objeto global Def para que tampoco tenga dependencias de inicialización inesperadas.

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

Si tiene que controlar estrictamente el orden de inicialización, además de simplemente asegurándose de que todo es inicializado antes de ser usado, poner todos los objetos globales en un conjunto unitario global de la siguiente manera.

struct Global 
{ 
    Abc abc; 
    Def def; 
}; 

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

Y hacer referencias a estos artículos de la siguiente manera:

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

Independientemente de cuál recibe llamadas en primer lugar, C++ reglas miembro de inicialización que garantiza que la ABC y objetos def se inicializan en el orden en que están definido en la clase Global.

Cuestiones relacionadas