Estoy tratando de entender qué sucede cuando los módulos con variables globales y estáticas se vinculan dinámicamente a una aplicación. Por módulos, me refiero a cada proyecto en una solución (¡trabajo mucho con Visual Studio!). Estos módulos están integrados en * .lib o * .dll o en el * .exe mismo.
Entiendo que el binario de una aplicación contiene datos globales y estáticos de todas las unidades de traducción individuales (archivos de objetos) en el segmento de datos (y solo segmento de datos de lectura si es constante).
¿Qué sucede cuando esta aplicación usa un módulo A con enlace dinámico de tiempo de carga? Supongo que la DLL tiene una sección para sus globales y estática. ¿El sistema operativo los carga? Si es así, ¿dónde se cargan?
¿Y qué sucede cuando la aplicación usa un módulo B con enlace dinámico en tiempo de ejecución?
Si tengo dos módulos en mi aplicación que usan A y B, ¿se crean copias de los globales de A y B como se menciona a continuación (si son procesos diferentes)?
¿Las DLL A y B tienen acceso a las aplicaciones globales?
(Indique sus razones también)
Citando de MSDN :
Las variables que se declaran como globales en un archivo de código fuente DLL son tratadas como variables globales por el compilador y el vinculador, pero cada proceso que carga una DLL determinada obtiene su propia copia de las variables globales de esa DLL. El alcance de las variables estáticas se limita al bloque en el que se declaran las variables estáticas. Como resultado, cada proceso tiene su propia instancia de las variables globales y estáticas DLL por defecto.
y desde aquí :
Al vincular dinámicamente módulos, no puede quedar claro si las diferentes bibliotecas tienen sus propias instancias de globales o si las globales son compartidas.
Gracias.
Respuestas:
Esta es una diferencia bastante famosa entre Windows y sistemas similares a Unix.
No importa qué:
Entonces, la cuestión clave aquí es realmente visibilidad .
En todos los casos,
static
las variables globales (o funciones) nunca son visibles desde fuera de un módulo (dll / so o ejecutable). El estándar C ++ requiere que estos tengan un enlace interno, lo que significa que no son visibles fuera de la unidad de traducción (que se convierte en un archivo objeto) en el que están definidos. Entonces, eso resuelve ese problema.Donde se complica es cuando tienes
extern
variables globales. Aquí, los sistemas similares a Windows y Unix son completamente diferentes.En el caso de Windows (.exe y .dll), las
extern
variables globales no son parte de los símbolos exportados. En otras palabras, los diferentes módulos no son conscientes de las variables globales definidas en otros módulos. Esto significa que obtendrá errores de enlazador si intenta, por ejemplo, crear un ejecutable que se supone que usa unaextern
variable definida en una DLL, porque esto no está permitido. Debería proporcionar un archivo de objeto (o biblioteca estática) con una definición de esa variable externa y vincularlo estáticamente con ambos el ejecutable y el archivo DLL, lo que resulta en dos distintas variables globales (uno perteneciente al ejecutable y uno que pertenecen a la DLL )Para exportar realmente una variable global en Windows, debe usar una sintaxis similar a la sintaxis de exportación / importación de funciones, es decir:
Cuando hace eso, la variable global se agrega a la lista de símbolos exportados y se puede vincular como todas las demás funciones.
En el caso de entornos similares a Unix (como Linux), las bibliotecas dinámicas, llamadas "objetos compartidos" con extensión,
.so
exportan todasextern
las variables (o funciones) globales. En este caso, si realiza un enlace de tiempo de carga desde cualquier lugar a un archivo de objeto compartido, las variables globales se comparten, es decir, se unen como una sola. Básicamente, los sistemas tipo Unix están diseñados para que no haya prácticamente ninguna diferencia entre vincular con una biblioteca estática o dinámica. Una vez más, la ODR se aplica en todos los ámbitos: unaextern
variable global se compartirá entre los módulos, lo que significa que solo debe tener una definición en todos los módulos cargados.Finalmente, en ambos casos, para sistemas similares a Windows o Unix, puede hacer un enlace en tiempo de ejecución de la biblioteca dinámica, es decir, usando
LoadLibrary()
/GetProcAddress()
/FreeLibrary()
odlopen()
/dlsym()
/dlclose()
. En ese caso, debe obtener manualmente un puntero a cada uno de los símbolos que desea usar, y eso incluye las variables globales que desea usar. Para las variables globales, puede usarGetProcAddress()
odlsym()
lo mismo que para las funciones, siempre que las variables globales sean parte de la lista de símbolos exportados (según las reglas de los párrafos anteriores).Y, por supuesto, como una nota final necesaria: se deben evitar las variables globales . Y creo que el texto que citó (acerca de que las cosas están "poco claras") se refiere exactamente a las diferencias específicas de la plataforma que acabo de explicar (las bibliotecas dinámicas no están realmente definidas por el estándar C ++, este es un territorio específico de la plataforma, lo que significa que es mucho menos confiable / portátil).
fuente
__attribute__((visibility("default")))
), A / B compartirá el mismo st_var. Pero si la clase se define con__attribute__((visibility("hidden")))
, entonces el módulo A y el módulo B tendrán su propia copia, no compartida.