programación - Encontrar problemas de orden de inicialización estática de C++
manual completo de c++ pdf (11)
Hemos tenido algunos problemas con el fiasco de orden de inicialización estática , y estoy buscando formas de analizar un montón de código para encontrar posibles ocurrencias. ¿Alguna sugerencia sobre cómo hacer esto de manera eficiente?
Editar: obtengo algunas buenas respuestas sobre cómo RESOLVER el problema de la orden de inicialización estática, pero esa no es realmente mi pregunta. Me gustaría saber cómo ENCONTRAR objetos que están sujetos a este problema. La respuesta de Evan parece ser la mejor hasta ahora en este sentido; No creo que podamos usar valgrind, pero podemos tener herramientas de análisis de memoria que podrían realizar una función similar. Eso atraparía problemas solo cuando el orden de inicialización es incorrecto para una compilación determinada, y la orden puede cambiar con cada compilación. Tal vez haya una herramienta de análisis estático que atrape esto. Nuestra plataforma es un compilador IBM XLC / C ++ que se ejecuta en AIX.
Hemos tenido algunos problemas con el fiasco de orden de inicialización estática, y estoy buscando formas de analizar un montón de código para encontrar posibles ocurrencias. ¿Alguna sugerencia sobre cómo hacer esto de manera eficiente?
No es un problema trivial, pero al menos puede hacerlo siguiendo pasos bastante simples si tiene una representación de su código de formato intermedio fácil de analizar.
1) Encuentra todos los globales que tienen constructores no triviales y ponlos en una lista.
2) Para cada uno de estos objetos no construidos trivialmente, genere todo el árbol de función potencial llamado por sus constructores.
3) Recorra el árbol de funciones constructor no trivial y si el código hace referencia a otros elementos globales construidos de forma no trivial (que son muy útiles en la lista que generó en el paso uno), tiene un posible orden inicial de inicialización estática problema.
4) Repita los pasos 2 y 3 hasta que haya agotado la lista generada en el paso 1.
Nota: puede optimizar esto solo visitando el árbol de función potencial una vez por clase de objeto en lugar de una vez por instancia global si tiene múltiples globales de una sola clase.
Resolviendo el orden de inicialización:
En primer lugar, esto es solo una solución temporal porque tienes variables globales de las que estás tratando de deshacerte pero que aún no has tenido tiempo (¿te vas a deshacer de ellas eventualmente?) :-)
class A
{
public:
// Get the global instance abc
static A& getInstance_abc() // return a reference
{
static A instance_abc;
return instance_abc;
}
};
Esto garantizará que se inicialice en el primer uso y se destruya cuando la aplicación finalice.
Problema Multi-Roscado:
C ++ 11 garantiza que esto es seguro para subprocesos:
§6.7 [stmt.dcl] p4
Si el control ingresa la declaración simultáneamente mientras se está inicializando la variable, la ejecución concurrente deberá esperar hasta que se complete la inicialización.
Sin embargo, C ++ 03 no garantiza oficialmente que la construcción de objetos de función estática es segura para subprocesos. Entonces, técnicamente, el método getInstance_XXX()
debe protegerse con una sección crítica. En el lado positivo, gcc tiene un parche explícito como parte del compilador que garantiza que cada objeto de función estática solo se inicializará una vez, incluso en presencia de subprocesos.
Tenga en cuenta: No use el patrón de bloqueo doblemente comprobado para tratar de evitar el costo del bloqueo. Esto no funcionará en C ++ 03.
Problemas de creación:
En la creación, no hay problemas porque garantizamos que se crea antes de que se pueda usar.
Problemas de destrucción:
Existe un problema potencial de acceso al objeto después de que ha sido destruido. Esto solo ocurre si accede al objeto desde el destructor de otra variable global (por global, me refiero a cualquier variable estática no local).
La solución es asegurarte de forzar el orden de destrucción.
Recuerde que el orden de destrucción es el inverso exacto del orden de construcción. Entonces, si accede al objeto en su destructor, debe garantizar que el objeto no haya sido destruido. Para hacer esto, debe garantizar que el objeto esté completamente construido antes de que se construya el objeto llamante.
class B
{
public:
static B& getInstance_Bglob;
{
static B instance_Bglob;
return instance_Bglob;;
}
~B()
{
A::getInstance_abc().doSomthing();
// The object abc is accessed from the destructor.
// Potential problem.
// You must guarantee that abc is destroyed after this object.
// To guarantee this you must make sure it is constructed first.
// To do this just access the object from the constructor.
}
B()
{
A::getInstance_abc();
// abc is now fully constructed.
// This means it was constructed before this object.
// This means it will be destroyed after this object.
// This means it is safe to use from the destructor.
}
};
Acabo de escribir un poco de código para rastrear este problema. Tenemos una base de código de buen tamaño (más de 1000 archivos) que funcionaba bien en Windows / VC ++ 2005, pero que se bloqueaba al iniciarse en Solaris / gcc. Escribí el siguiente archivo .h:
#ifndef FIASCO_H
#define FIASCO_H
/////////////////////////////////////////////////////////////////////////////////////////////////////
// [WS 2010-07-30] Detect the infamous "Static initialization order fiasco"
// email warrenstevens --> [initials]@[firstnamelastname].com
// read --> http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 if you haven''t suffered
// To enable this feature --> define E-N-A-B-L-E-_-F-I-A-S-C-O-_-F-I-N-D-E-R, rebuild, and run
#define ENABLE_FIASCO_FINDER
/////////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef ENABLE_FIASCO_FINDER
#include <iostream>
#include <fstream>
inline bool WriteFiasco(const std::string& fileName)
{
static int counter = 0;
++counter;
std::ofstream file;
file.open("FiascoFinder.txt", std::ios::out | std::ios::app);
file << "Starting to initialize file - number: [" << counter << "] filename: [" << fileName.c_str() << "]" << std::endl;
file.flush();
file.close();
return true;
}
// [WS 2010-07-30] If you get a name collision on the following line, your usage is likely incorrect
#define FIASCO_FINDER static const bool g_psuedoUniqueName = WriteFiasco(__FILE__);
#else // ENABLE_FIASCO_FINDER
// do nothing
#define FIASCO_FINDER
#endif // ENABLE_FIASCO_FINDER
#endif //FIASCO_H
y dentro de cada archivo .cpp en la solución, agregué esto:
#include "PreCompiledHeader.h" // (which #include''s the above file)
FIASCO_FINDER
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"
Cuando ejecutas tu aplicación, obtendrás un archivo de salida así:
Starting to initialize file - number: [1] filename: [p://OneFile.cpp]
Starting to initialize file - number: [2] filename: [p://SecondFile.cpp]
Starting to initialize file - number: [3] filename: [p://ThirdFile.cpp]
Si experimenta un bloqueo, el culpable debería estar en el último archivo .cpp de la lista. Y al menos, esto le dará un buen lugar para establecer puntos de interrupción, ya que este código debe ser el primero absoluto de su código para ejecutar (después de lo cual puede recorrer su código y ver todos los globales que se están inicializando) .
Notas:
Es importante que coloque la macro "FIASCO_FINDER" lo más cerca posible de su archivo. Si lo coloca debajo de otros # incluye, corre el riesgo de que se cuelgue antes de identificar el archivo en el que se encuentra.
Si está usando Visual Studio y encabezados precompilados, agregue esta línea de macro adicional a todos sus archivos .cpp puede hacerlo rápidamente usando el diálogo Buscar y reemplazar para reemplazar su #include existente "precompiledheader.h" con el mismo texto más la línea FIASCO_FINDER (si marca "expresiones regulares", puede usar "/ n" para insertar texto de reemplazo de varias líneas)
Dependiendo de su compilador, puede colocar un punto de interrupción en el código de inicialización del constructor. En Visual C ++, esta es la función _initterm
, que tiene un puntero inicial y final de una lista de las funciones a llamar.
Luego ingrese a cada función para obtener el nombre del archivo y la función (suponiendo que haya compilado con la información de depuración). Una vez que tenga los nombres, salga de la función ( _initterm
copia de seguridad en _initterm
) y continúe hasta que _initterm
salga.
Eso le proporciona todos los inicializadores estáticos, no solo los que están en su código, es la forma más fácil de obtener una lista exhaustiva. Puede filtrar aquellos sobre los que no tiene control (como los de bibliotecas de terceros).
La teoría es válida para otros compiladores, pero el nombre de la función y la capacidad del depurador pueden cambiar.
Gimpel Software (www.gimpel.com) afirma que sus herramientas de análisis estático PC-Lint / FlexeLint detectarán tales problemas.
He tenido una buena experiencia con sus herramientas, pero no con este problema específico, así que no puedo responder por cuánto podrían ayudar.
Hay un código que esencialmente "inicializa" C ++ generado por el compilador. Una forma fácil de encontrar este código / la pila de llamadas en ese momento es crear un objeto estático con algo que elimine la referencia NULL en el constructor: interrumpa el depurador y explore un poco. El compilador de MSVC configura una tabla de punteros de función que se repite para la inicialización estática. Debería poder acceder a esta tabla y determinar toda la inicialización estática que tiene lugar en su programa.
Lo primero que debe hacer es hacer una lista de todos los objetos estáticos que tienen constructores no triviales.
Teniendo en cuenta eso, debe taponarlos de uno en uno o simplemente reemplazarlos con objetos de patrón único.
El patrón singleton es objeto de muchas críticas, pero la construcción perezosa "según sea necesario" es una manera bastante fácil de solucionar la mayoría de los problemas ahora y en el futuro.
antiguo...
MyObject myObject
nuevo...
MyObject &myObject()
{
static MyObject myActualObject;
return myActualObject;
}
Por supuesto, si su aplicación es de subprocesos múltiples, esto puede causarle más problemas de los que tenía en primer lugar ...
Otras respuestas son correctas, solo quería agregar que el getter del objeto debería implementarse en un archivo .cpp y no debería ser estático. Si lo implementa en un archivo de encabezado, el objeto se creará en cada biblioteca / marco desde el que lo llame ...
Reemplace todos los objetos globales con funciones globales que devuelven una referencia a un objeto declarado estático en la función. Esto no es seguro para subprocesos, por lo que si tu aplicación tiene varios subprocesos, es posible que necesites algunos trucos como pthread_once o un bloqueo global. Esto asegurará que todo se inicialice antes de ser utilizado.
Ahora bien, tu programa funciona (¡hurra!) O bien se encuentra en un bucle infinito porque tienes una dependencia circular (se necesita un rediseño), o bien pasas al siguiente error.
Si su proyecto está en Visual Studio (lo he intentado con VC ++ Express 2005 y con Visual Studio 2008 Pro):
- Abrir vista de clase (Menú principal-> Ver-> Vista de clase)
- Expanda cada proyecto en su solución y haga clic en "Funciones y variables globales"
Esto debería darte una lista decente de todos los globales que están sujetos al fiasco .
Al final, un mejor enfoque es tratar de eliminar estos objetos de su proyecto (más fácil decirlo que hacerlo, a veces).
quizás use valgrind para encontrar el uso de la memoria no inicializada. La mejor solución para el "fiasco de orden de inicialización estática" es usar una función estática que devuelve una instancia del objeto como esta:
class A {
public:
static X &getStatic() { static X my_static; return my_static; }
};
De esta forma puede acceder a su objeto estático llamando a getStatic, esto garantizará que se inicialice en el primer uso.
Si necesita preocuparse por el orden de la inicialización, devuelva un objeto nuevo en vez de un objeto estáticamente asignado.
EDITAR: eliminó el objeto estático redundante, no sé por qué, pero mezclé y combiné dos métodos para tener una estática en mi ejemplo original.