static function c++
c++ linux doble destrucción de variable estática. los símbolos de enlace se superponen (4)
Entorno: linux x64, compilador gcc 4.x
El proyecto tiene la siguiente estructura:
static library "slib"
-- inside this library, there is static object "sobj"
dynamic library "dlib"
-- links statically "slib"
executable "exe":
-- links "slib" statically
-- links "dlib" dynamically
Al final del programa, "sobj" se destruye dos veces. Ese comportamiento es esperado, PERO se destruye dos veces en la misma dirección de memoria, es decir, el mismo "esto" en el destructor; como resultado, hay un problema de doble destrucción. Creo que se debe a algún símbolo superpuesto.
¿Cuál es la solución para ese conflicto? Tal vez alguna opción de enlace?
Aquí está el caso de prueba:
main_exe.cpp
#include <cstdlib>
#include "static_lib.h"
#include "dynamic_lib.h"
int main(int argc, char *argv[])
{
stat_useStatic();
din_useStatic();
return EXIT_SUCCESS;
}
static_lib.h
#ifndef STATIC_LIB_H
#define STATIC_LIB_H
#include <cstdio>
void stat_useStatic();
struct CTest
{
CTest(): status(isAlive)
{
printf("CTest() this=%d/n",this);
}
~CTest()
{
printf("~CTest() this=%d, %s/n",this,status==isAlive?"is Alive":"is Dead");
status=isDead;
}
void use()
{
printf("use/n");
}
static const int isAlive=12385423;
static const int isDead=6543421;
int status;
static CTest test;
};
#endif
static_lib.cpp
#include "static_lib.h"
CTest CTest::test;
void stat_useStatic()
{
CTest::test.use();
}
dynamic_lib.h
#ifndef DYNAMIC_LIB_H
#define DYNAMIC_LIB_H
#include "static_lib.h"
#ifdef WIN32
#define DLLExport __declspec(dllexport)
#else
#define DLLExport
#endif
DLLExport void din_useStatic();
#endif
dynamic_lib.cpp
#include "dynamic_lib.h"
DLLExport void din_useStatic()
{
CTest::test.use();
}
CMakeLists.txt
project( StaticProblem )
cmake_minimum_required(VERSION 2.6)
if(WIN32)
else(WIN32)
ADD_DEFINITIONS(-fPIC)
endif(WIN32)
ADD_LIBRARY( static_lib STATIC static_lib.cpp static_lib.h)
ADD_LIBRARY( dynamic_lib SHARED dynamic_lib.cpp dynamic_lib.h)
TARGET_LINK_LIBRARIES( dynamic_lib static_lib )
ADD_EXECUTABLE( main_exe main_exe.cpp )
TARGET_LINK_LIBRARIES( main_exe static_lib dynamic_lib )
Ese ejemplo funciona bien, en Windows, pero en Linux, hay un problema. Como funciona bien en Windows, la solución debería ser cambiar una opción de enlace o algo así, pero no cambiar la estructura del proyecto o no usar vars estáticos.
Salida:
Windows
CTest() this=268472624
CTest() this=4231488
use
use
~CTest() this=4231488, is Alive
~CTest() this=268472624, is Alive
Linux
CTest() this=6296204
CTest() this=6296204
use
use
~CTest() this=6296204, is Alive
~CTest() this=6296204, is Dead
Es difícil decirlo sin ver ningún código, pero este territorio (bibliotecas cargadas dinámicamente) no está cubierto explícitamente por la norma, por lo que es muy posible que diferentes implementaciones manejen los casos secundarios de manera diferente.
¿No puede simplemente evitar esta confusión, por ejemplo, utilizando diferentes espacios de nombres para las dos instancias de la biblioteca estática (por ejemplo, haciendo que el espacio de nombres se use para el objeto estático definido por una opción de línea de comandos)?
OK, he encontrado solución:
http://gcc.gnu.org/wiki/Visibility
Por ejemplo si cambio
static CTest test;
a
__attribute__ ((visibility ("hidden"))) static CTest test;
el problema se irá Linux:
CTest() this=-1646158468
CTest() this=6296196
use
use
~CTest() this=6296196, is Alive
~CTest() this=-1646158468, is Alive
La salida de nm antes de la corrección era:
0000000000200dd4 B _ZN5CTest4testE
después de arreglar
0000000000200d7c b _ZN5CTest4testE
La diferencia se cambia el símbolo global "B" al símbolo local "b".
En lugar de agregar " atributo ((visibilidad (" oculto "))) a los símbolos, es posible usar la opción del compilador" -fvisibilidad = oculta ". Esa opción hace que gcc se comporte mucho más como Windows Env.
Por cierto, si define var estática dentro de la función stat_useStatic, será solo una instancia de esa var estática en todo el programa en linux (pero dos instancias en Windows), y eso es lo que estamos usando para solucionar ese problema. Aqui estan los cambios
void stat_useStatic()
{
static CTest stest;
stest.use();
CTest::test.use();
}
DLLExport void din_useStatic()
{
stat_useStatic();
CTest::test.use();
}
Ahora, el comportamiento de Linux y Windows difiere aún más:
Windows
CTest() this=268476728
CTest() this=4235592
CTest() this=4235584
use
use
CTest() this=268476720
use
use
use
~CTest() this=4235584, is Alive
~CTest() this=4235592, is Alive
~CTest() this=268476720, is Alive
~CTest() this=268476728, is Alive
Linux
CTest() this=6296376
CTest() this=6296376
CTest() this=6296392
use
use
use
use
use
~CTest() this=6296392, is Alive
~CTest() this=6296376, is Alive
~CTest() this=6296376, is Dead
Como puede ver, Linux crea solo una var. Estática, pero Windows crea dos instancias.
De verdad, parece que Linux no debe crear y destruir doble static var en el primer caso, por su lógica, igual que en el segundo caso (static dentro de func).
El uso de la función local static está en lugar de la clase static es solo una solución, no una solución real. Porque la fuente de la biblioteca puede no estar disponible.
TL; DR: no debe vincular una biblioteca una vez como una dependencia estática y una vez como una dependencia dinámica.
¿Cómo se ejecutan los destructores de variables estáticas en el ABI de Itanium (utilizado por clang, gcc, icc ...)?
La biblioteca estándar de C ++ ofrece una instalación estándar para programar la ejecución de una función durante el cierre del programa ( después de que atexit
main) en el formato de atexit
.
El comportamiento es relativamente simple, atexit
básicamente construye una pila de devoluciones de llamada y, por lo tanto, las ejecutará en el orden inverso a su programación.
Cada vez que se construye una variable estática, inmediatamente después de que finaliza su construcción, se registra una devolución de llamada en la pila atexit
para destruirla durante el apagado.
¿Qué sucede cuando existe una variable estática tanto en una biblioteca enlazada estáticamente como en una biblioteca enlazada dinámicamente?
Intenta existir dos veces.
Cada biblioteca tendrá:
- un área de memoria reservada para la variable, señalada por el símbolo correspondiente (el nombre mutilado de la variable),
- una entrada en la sección de carga para construir la variable y programar su destrucción.
La sorpresa viene de la forma en que funciona la resolución de símbolos en el cargador. Esencialmente, el cargador construye un mapeo entre el símbolo y la ubicación (puntero), por orden de llegada.
Sin embargo, las secciones de carga / descarga no tienen nombre y, por lo tanto, cada una de ellas se ejecuta en su totalidad.
Por lo tanto:
- la variable estática se construye por primera vez,
- la variable estática se construye una segunda vez sobre la primera (que se filtra),
- la variable estática se destruye por primera vez,
- la variable estática se destruye una segunda vez; que es generalmente donde se detecta el problema.
¿Y qué?
La solución es simple: NO se vincula con una biblioteca estática A (directamente) y una biblioteca dinámica B también se vincula contra A (de forma dinámica o estática).
Dependiendo del caso de uso, usted puede:
- enlazar estáticamente contra B,
- enlazar dinámicamente contra A y B.
Como funciona bien en Windows, la solución debería ser cambiar una opción de enlace o algo así, pero no cambiar la estructura del proyecto o no usar vars estáticos.
En el improbable caso de que realmente necesite dos instancias independientes de la variable estática, además de refactorizar su código, es posible ocultar los símbolos en su biblioteca dinámica.
El comportamiento predeterminado de este Windows, que es la razón por la cual el atributo DLLExport
es requerido allí, y porque desde que fue olvidado por CTest::test
el comportamiento en Windows es diferente.
Sin embargo, tenga en cuenta que cualquier futuro mantenedor de este proyecto lo maldecirá con fuerza si opta por este comportamiento. Nadie espera que una variable estática tenga múltiples instancias.