¿Qué sucede con las variables globales y estáticas en una biblioteca compartida cuando está vinculada dinámicamente?

Resuelto Raja asked hace 11 años • 3 respuestas

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 propio *.exe.

Entiendo que el binario de una aplicación contiene datos globales y estáticos de todas las unidades de traducción individuales (archivos objeto) en el segmento de datos (y segmento de datos de solo lectura si es constante).

  • ¿Qué sucede cuando esta aplicación utiliza un módulo A con enlace dinámico en tiempo de carga? Supongo que la DLL tiene una sección para datos globales y estáticos. ¿Los carga el sistema operativo? Si es así, ¿dónde los cargan?

  • ¿Y qué sucede cuando la aplicación utiliza 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 obtienen acceso a las aplicaciones globales?

(Por favor indique también sus razones)

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 estáticas y globales de la DLL de forma predeterminada.

y desde aquí :

Al vincular módulos dinámicamente, puede no estar claro si las diferentes bibliotecas tienen sus propias instancias de globales o si los globales son compartidos.

Gracias.

Raja avatar Oct 15 '13 10:10 Raja
Aceptado

Esta es una diferencia bastante famosa entre Windows y los sistemas tipo Unix.

No importa qué:

  • Cada proceso tiene su propio espacio de direcciones, lo que significa que nunca se comparte memoria entre procesos (a menos que utilice alguna biblioteca o extensión de comunicación entre procesos).
  • La regla de una definición (ODR) todavía se aplica, lo que significa que solo puede tener una definición de la variable global visible en el momento del enlace (enlace estático o dinámico).

Entonces, la cuestión clave aquí es realmente la visibilidad .

En todos los casos, staticlas variables (o funciones) globales nunca son visibles desde fuera de un módulo (dll/so o ejecutable). El estándar C++ requiere que 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 la que están definidos. Entonces, eso resuelve ese problema.

Donde se complica es cuando tienes externvariables globales. Aquí, los sistemas Windows y Unix son completamente diferentes.

En el caso de Windows (.exe y .dll), las externvariables globales no forman parte de los símbolos exportados. En otras palabras, los diferentes módulos no conocen de ninguna manera las variables globales definidas en otros módulos. Esto significa que obtendrá errores del vinculador si intenta, por ejemplo, crear un ejecutable que se supone que utiliza una externvariable definida en una DLL, porque esto no está permitido. Debería proporcionar un archivo objeto (o biblioteca estática) con una definición de esa variable externa y vincularla estáticamente tanto con el ejecutable como con la DLL, lo que da como resultado dos variables globales distintas (una que pertenece al ejecutable y otra a la DLL). ).

Para exportar realmente una variable global en Windows, debe usar una sintaxis similar a la sintaxis de la función exportar/importar, es decir:

#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif

MY_DLL_EXPORT int my_global;

Cuando haces 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 tipo Unix (como Linux), las bibliotecas dinámicas, llamadas "objetos compartidos" con extensión, .soexportan todas externlas variables (o funciones) globales. En este caso, si realiza un enlace en tiempo de carga desde cualquier lugar a un archivo objeto compartido, entonces las variables globales se comparten, es decir, se enlazan entre sí como una sola. Básicamente, los sistemas tipo Unix están diseñados para que prácticamente no haya diferencia entre vincularse con una biblioteca estática o dinámica. Nuevamente, ODR se aplica en todos los ámbitos: una externvariable global se compartirá entre módulos, lo que significa que debe tener solo una definición en todos los módulos cargados.

Finalmente, en ambos casos, para sistemas Windows o tipo Unix, puede realizar un enlace en tiempo de ejecución de la biblioteca dinámica, es decir, utilizando LoadLibrary()/ GetProcAddress()/ FreeLibrary()o dlopen()// . 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 usar o igual que para las funciones, siempre que las variables globales formen parte de la lista de símbolos exportados (según las reglas de los párrafos anteriores).dlsym()dlclose()GetProcAddress()dlsym()

Y por supuesto, como nota final necesaria: se deben evitar las variables globales . Y creo que el texto que citó (sobre que las cosas "no están 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).

Mikael Persson avatar Oct 15 '2013 06:10 Mikael Persson

La respuesta de Mikael Persson, aunque muy exhaustiva, contiene un error grave (o al menos engañoso), en lo que respecta a las variables globales, que es necesario aclarar. La pregunta original preguntaba si había copias separadas de las variables globales o si las variables globales se compartían entre los procesos.

La verdadera respuesta es la siguiente: hay copias separadas (múltiples) de las variables globales para cada proceso y no se comparten entre procesos. Por lo tanto, afirmar que se aplica la regla de una definición (ODR) también es muy engañoso, no se aplica en el sentido de que NO son los mismos globales utilizados por cada proceso, por lo que en realidad no es "una definición" entre procesos .

Además, aunque las variables globales no son "visibles" para el proceso, siempre son fácilmente "accesibles" para el proceso, porque cualquier función podría devolver fácilmente un valor de una variable global al proceso o, en realidad, a un proceso. podría establecer un valor de una variable global a través de una llamada a función. Por tanto, esta respuesta también es engañosa.

En realidad, "sí", los procesos tienen "acceso" completo a los globales, al menos a través de las llamadas a funciones de la biblioteca. Pero para reiterar, cada proceso tiene su propia copia de los globales, por lo que no serán los mismos globales que utiliza otro proceso.

Por lo tanto, toda la respuesta relacionada con la exportación externa de productos globales realmente está fuera de tema, es innecesaria y ni siquiera está relacionada con la pregunta original. Debido a que no es necesario acceder a los globales, siempre se puede acceder a los globales indirectamente a través de llamadas a funciones a la biblioteca.

La única parte que se comparte entre los procesos, por supuesto, es el "código" real. El código solo se carga en un lugar de la memoria física (RAM), pero esa misma ubicación de memoria física, por supuesto, se asigna a las ubicaciones de memoria virtual "local" de cada proceso.

Por el contrario, una biblioteca estática tiene una copia del código para cada proceso ya integrado en el ejecutable (ELF, PE, etc.) y, por supuesto, al igual que las bibliotecas dinámicas, tiene globales separados para cada proceso.

Deckard 5 Pegasus avatar Jan 15 '2022 06:01 Deckard 5 Pegasus