Esta es una diferencia bastante famosa entre Windows y sistemas similares a Unix.
No importa qué:
- Cada proceso tiene su propio espacio de direcciones, lo que significa que nunca hay memoria compartida entre los procesos (a menos que use alguna biblioteca o extensiones 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 tiempo de enlace (enlace estático o dinámico).
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 una extern
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:
#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 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 todas extern
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: una extern
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()
o dlopen()
/ 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 usar GetProcAddress()
o dlsym()
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).