Al crear una biblioteca de clases en C ++, puede elegir entre bibliotecas dinámicas ( .dll
, .so
) y estáticas ( .lib
, .a
). ¿Cuál es la diferencia entre ellos y cuándo es apropiado usar cuál?
Al crear una biblioteca de clases en C ++, puede elegir entre bibliotecas dinámicas ( .dll
, .so
) y estáticas ( .lib
, .a
). ¿Cuál es la diferencia entre ellos y cuándo es apropiado usar cuál?
Respuestas:
Las bibliotecas estáticas aumentan el tamaño del código en su binario. Siempre se cargan y cualquier versión del código que compiló es la versión del código que se ejecutará.
Las bibliotecas dinámicas se almacenan y versionan por separado. Es posible que se cargue una versión de la biblioteca dinámica que no era la original que se envió con su código si la actualización se considera binariamente compatible con la versión original.
Además, las bibliotecas dinámicas no se cargan necesariamente, generalmente se cargan cuando se llaman por primera vez, y se pueden compartir entre componentes que usan la misma biblioteca (múltiples cargas de datos, una carga de código).
La mayoría de las veces se consideraba que las bibliotecas dinámicas eran el mejor enfoque, pero originalmente tenían una falla importante (google DLL hell), que prácticamente ha sido eliminada por los sistemas operativos Windows más recientes (Windows XP en particular).
Otros han explicado adecuadamente qué es una biblioteca estática, pero me gustaría señalar algunas de las advertencias del uso de bibliotecas estáticas, al menos en Windows:
Singletons: si algo debe ser global / estático y único, tenga mucho cuidado al colocarlo en una biblioteca estática. Si se vinculan varias DLL contra esa biblioteca estática, cada una obtendrá su propia copia del singleton. Sin embargo, si su aplicación es un único EXE sin archivos DLL personalizados, esto puede no ser un problema.
Eliminación de código sin referencia: cuando se vincula con una biblioteca estática, solo las partes de la biblioteca estática a las que hace referencia su DLL / EXE se vincularán a su DLL / EXE.
Por ejemplo, si mylib.lib
contiene a.obj
y b.obj
y su DLL / EXE solo hace referencia a funciones o variables a.obj
, b.obj
el enlazador descartará la totalidad de ellas . Si b.obj
contiene objetos globales / estáticos, sus constructores y destructores no se ejecutarán. Si esos constructores / destructores tienen efectos secundarios, puede ser decepcionado por su ausencia.
Del mismo modo, si la biblioteca estática contiene puntos de entrada especiales, es posible que deba asegurarse de que realmente estén incluidos. Un ejemplo de esto en la programación incrustada (está bien, no en Windows) sería un controlador de interrupciones marcado como una dirección específica. También debe marcar el controlador de interrupciones como un punto de entrada para asegurarse de que no se descarte.
Otra consecuencia de esto es que una biblioteca estática puede contener archivos de objetos que son completamente inutilizables debido a referencias no resueltas, pero no causará un error de enlace hasta que haga referencia a una función o variable de esos archivos de objetos. Esto puede suceder mucho después de que se escriba la biblioteca.
Símbolos de depuración: puede desear un PDB separado para cada biblioteca estática, o puede desear que los símbolos de depuración se coloquen en los archivos de objeto para que se enrollen en el PDB para la DLL / EXE. La documentación de Visual C ++ explica las opciones necesarias .
RTTI: puede terminar con varios type_info
objetos para la misma clase si vincula una única biblioteca estática en varias DLL. Si el programa asume que type_info
son datos "simples" y los usos &typeid()
o type_info::before()
, puede obtener resultados no deseados y sorprendentes.
Una lib es una unidad de código que se incluye dentro del ejecutable de su aplicación.
Un dll es una unidad independiente de código ejecutable. Se carga en el proceso solo cuando se realiza una llamada a ese código. Un dll puede ser utilizado por múltiples aplicaciones y cargado en múltiples procesos, mientras que solo tiene una copia del código en el disco duro.
Pros Dll : se puede utilizar para reutilizar / compartir código entre varios productos; cargar en la memoria de proceso a pedido y puede descargarse cuando no sea necesario; se puede actualizar independientemente del resto del programa.
Contras de dll: impacto en el rendimiento de la carga de dll y el rebase de código; problemas de versiones ("dll hell")
Pros profesionales : sin impacto en el rendimiento ya que el código siempre se carga en el proceso y no se modifica. Sin problemas de versiones.
Contras de Lib : ejecutable / proceso "bloat": todo el código está en su ejecutable y se carga al inicio del proceso; sin reutilizar / compartir: cada producto tiene su propia copia del código.
Además de las implicaciones técnicas de las bibliotecas estáticas vs dinámicas (los archivos estáticos agrupan todo en una gran biblioteca binaria vs dinámica que permite compartir código entre varios ejecutables diferentes), existen las implicaciones legales .
Por ejemplo, si está utilizando un código con licencia LGPL y se vincula estáticamente con una biblioteca LGPL (y, por lo tanto, crea un gran binario), su código se convierte automáticamente en código LGPL de fuente abierta ( libre como en libertad) . Si se vincula con objetos compartidos, solo necesita LGPL las mejoras / correcciones de errores que realice en la biblioteca LGPL.
Esto se convierte en un tema mucho más importante si está decidiendo cómo compilar sus aplicaciones móviles, por ejemplo (en Android tiene la opción de estático frente a dinámico, en iOS no, siempre es estático).
Los programas C ++ se construyen en dos fases.
La biblioteca estática (.lib) es solo un paquete de archivos .obj y, por lo tanto, no es un programa completo. No ha pasado por la segunda fase (vinculación) de la construcción de un programa. Dlls, por otro lado, son como exe's y, por lo tanto, son programas completos.
Si construye una biblioteca estática, aún no está vinculada y, por lo tanto, los consumidores de su biblioteca estática tendrán que usar el mismo compilador que usó (si usó g ++, tendrán que usar g ++).
Si, en cambio, compiló un dll (y lo compiló correctamente ), ha creado un programa completo que todos los consumidores pueden usar, sin importar qué compilador estén utilizando. Sin embargo, existen varias restricciones para exportar desde un archivo dll, si se desea compatibilidad cruzada con el compilador.
consumers of your static library will have to use the same compiler that you used
si la biblioteca estática usa la biblioteca C ++, como #include <iostream>
.
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
Una biblioteca estática se compila en el cliente. Un .lib se usa en tiempo de compilación y el contenido de la biblioteca se convierte en parte del ejecutable consumidor.
Una biblioteca dinámica se carga en tiempo de ejecución y no se compila en el ejecutable del cliente. Las bibliotecas dinámicas son más flexibles ya que múltiples ejecutables de clientes pueden cargar un archivo DLL y utilizar su funcionalidad. Esto también mantiene el tamaño general y la capacidad de mantenimiento de su código de cliente al mínimo.
Debe pensar cuidadosamente sobre los cambios a lo largo del tiempo, el control de versiones, la estabilidad, la compatibilidad, etc.
Si hay dos aplicaciones que usan el código compartido, ¿desea forzar que esas aplicaciones cambien juntas, en caso de que necesiten ser compatibles entre sí? Luego usa el dll. Todos los exe utilizarán el mismo código.
¿O quieres aislarlos unos de otros, para que puedas cambiar uno y estar seguro de que no has roto el otro? Luego usa la lib estática.
El infierno de DLL es cuando probablemente DEBERÍAS HABER usado una lib estática, pero en su lugar usaste un dll, y no todos los exes son compatibles con él.
Una biblioteca estática debe estar vinculada al ejecutable final; se convierte en parte del ejecutable y lo sigue a donde quiera que vaya. Se carga una biblioteca dinámica cada vez que se ejecuta el ejecutable y permanece separada del ejecutable como un archivo DLL.
Debería usar una DLL cuando desee poder cambiar la funcionalidad proporcionada por la biblioteca sin tener que volver a vincular el ejecutable (simplemente reemplace el archivo DLL, sin tener que reemplazar el archivo ejecutable).
Usaría una biblioteca estática siempre que no tenga una razón para usar una biblioteca dinámica.
El artículo de Ulrich Drepper sobre " Cómo escribir bibliotecas compartidas " también es un buen recurso que detalla la mejor manera de aprovechar las bibliotecas compartidas, o lo que él denomina "Objetos dinámicos compartidos" (DSO). Se centra más en las bibliotecas compartidas en el formato binario ELF , pero algunas discusiones también son adecuadas para las DLL de Windows.
Para una excelente discusión sobre este tema, lea este artículo de Sun.
Incluye todos los beneficios, incluida la posibilidad de insertar bibliotecas interpuestas. Puede encontrar más detalles sobre la interposición en este artículo aquí .
Realmente, la compensación que está haciendo (en un proyecto grande) está en el tiempo de carga inicial, las bibliotecas se vincularán en un momento u otro, la decisión que se debe tomar es si el enlace tomará el tiempo suficiente para que el compilador lo necesite para morder la bala y hacerlo por adelantado, o el enlazador dinámico puede hacerlo en el momento de la carga.
Si su biblioteca se va a compartir entre varios ejecutables, a menudo tiene sentido hacerlo dinámico para reducir el tamaño de los ejecutables. De lo contrario, definitivamente hazlo estático.
Hay varias desventajas de usar un dll. Hay gastos generales adicionales para cargarlo y descargarlo. También hay una dependencia adicional. Si cambia el dll para que sea incompatible con sus ejecutables, dejarán de funcionar. Por otro lado, si cambia una biblioteca estática, sus ejecutables compilados usando la versión anterior no se verán afectados.
Si la biblioteca es estática, en el momento del enlace, el código está vinculado con su ejecutable. Esto hace que tu ejecutable sea más grande (que si fueras a la ruta dinámica).
Si la biblioteca es dinámica, en el momento del enlace se incorporan referencias a los métodos requeridos en su ejecutable. Esto significa que debe enviar su ejecutable y la biblioteca dinámica. También debe considerar si el acceso compartido al código en la biblioteca es seguro, la dirección de carga preferida, entre otras cosas.
Si puede vivir con la biblioteca estática, vaya con la biblioteca estática.
Usamos muchas DLL (> 100) en nuestro proyecto. Estas DLL tienen dependencias entre sí y, por lo tanto, elegimos la configuración de la vinculación dinámica. Sin embargo, tiene las siguientes desventajas:
Tal vez una mejor configuración fue hacer de todo una biblioteca estática (y, por lo tanto, solo tiene un ejecutable). Esto funciona solo si no se produce una duplicación de código. Una prueba parece respaldar esta suposición, pero no pude encontrar una cotización oficial de MSDN. Entonces, por ejemplo, haga 1 exe con:
El código y las variables de shared_lib2 deben estar presentes en el ejecutable combinado final solo una vez. ¿Alguien puede apoyar esta pregunta?
Las bibliotecas estáticas son archivos que contienen el código objeto de la biblioteca, cuando se vincula a una aplicación, ese código se compila en el ejecutable. Las bibliotecas compartidas son diferentes en el sentido de que no se compilan en el ejecutable. En cambio, el vinculador dinámico busca en algunos directorios buscando la (s) biblioteca (s) que necesita, luego lo carga en la memoria. Más de un ejecutable puede usar la misma biblioteca compartida al mismo tiempo, lo que reduce el uso de memoria y el tamaño del ejecutable. Sin embargo, hay más archivos para distribuir con el ejecutable. Debe asegurarse de que la biblioteca esté instalada en el sistema de usos en algún lugar donde el vinculador pueda encontrarla, la vinculación estática elimina este problema pero da como resultado un archivo ejecutable más grande.
Si su trabajo en proyectos embebidos o plataformas especializadas, las bibliotecas estáticas son el único camino a seguir, también muchas veces son menos difíciles de compilar en su aplicación. También tener proyectos y archivos MAKE que incluyen todo hace la vida más feliz.
Daría una regla general que dice que si tiene una base de código grande, todo construido sobre bibliotecas de nivel inferior (por ejemplo, un marco Utils o Gui), que desea dividir en bibliotecas más manejables y luego convertirlas en bibliotecas estáticas. Las bibliotecas dinámicas realmente no te compran nada y hay menos sorpresas: solo habrá una instancia de singletons, por ejemplo.
Si tiene una biblioteca que está completamente separada del resto de la base de código (por ejemplo, una biblioteca de terceros), considere hacerla una dll. Si la biblioteca es LGPL, es posible que deba usar un dll de todos modos debido a las condiciones de licencia.