c++ - programacion - ¿Por qué el orden en que se vinculan las bibliotecas a veces causa errores en GCC?
librerias estaticas y dinamicas c++ (10)
¿Por qué el orden en que se vinculan las bibliotecas a veces causa errores en GCC?
El enlazador ld de GNU es un denominado enlazador inteligente. Mantendrá un seguimiento de las funciones utilizadas por las bibliotecas estáticas anteriores, eliminando de forma permanente aquellas funciones que no se utilizan en sus tablas de búsqueda. El resultado es que si vincula una biblioteca estática demasiado pronto, las funciones de esa biblioteca ya no estarán disponibles para las bibliotecas estáticas más adelante en la línea de enlace.
El enlazador de UNIX típico funciona de izquierda a derecha, así que ponga todas las bibliotecas dependientes a la izquierda y las que satisfacen esas dependencias a la derecha de la línea de enlace. Es posible que algunas bibliotecas dependan de otras, mientras que otras bibliotecas dependen de ellas. Aquí es donde se complica. ¡Cuando se trata de referencias circulares, arregla tu código!
El orden de los enlaces ciertamente importa, al menos en algunas plataformas. He visto bloqueos para aplicaciones vinculadas con bibliotecas en un orden incorrecto (donde equivocado significa A vinculado antes que B pero B depende de A).
Este es un ejemplo para aclarar cómo funcionan las cosas con GCC cuando se trata de bibliotecas estáticas . Asumamos que tenemos el siguiente escenario:
-
myprog.o
- que contiene la funciónmain()
, dependiente delibmysqlclient
-
libmysqlclient
- estático, por el ejemplo (preferiría la biblioteca compartida, por supuesto, ya que ellibmysqlclient
es enorme); en/usr/local/lib
; y dependiente de cosas delibz
-
libz
(dinámico)
¿Cómo vinculamos esto? (Nota: ejemplos de compilación en Cygwin usando gcc 4.3.4)
gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init''
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line
gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress''
# we have to link with libz, too
gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress''
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line
gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works
He visto esto mucho, algunos de nuestros módulos enlazan más de 100 bibliotecas de nuestro código más el sistema y las librerías de terceros.
Dependiendo de los diferentes enlazadores HP / Intel / GCC / SUN / SGI / IBM / etc, puede obtener funciones / variables no resueltas, etc. En algunas plataformas debe listar las bibliotecas dos veces.
En su mayor parte, utilizamos una jerarquía estructurada de bibliotecas, núcleo, plataforma, diferentes capas de abstracción, pero para algunos sistemas todavía tiene que jugar con el orden en el comando de enlace.
Una vez que se encuentra con una solución, documéntelo para que el siguiente desarrollador no tenga que resolverlo nuevamente.
Mi antiguo profesor solía decir, " alta cohesión y bajo acoplamiento ", sigue siendo cierto hoy en día.
Me imagino que es porque algunas de esas bibliotecas tienen dependencias de otras bibliotecas, y si no se han vinculado aún, entonces obtendría errores de vinculador.
Otra alternativa sería especificar la lista de bibliotecas dos veces:
gcc prog.o libA.a libB.a libA.a libB.a -o prog.x
Al hacer esto, no tiene que preocuparse por la secuencia correcta ya que la referencia se resolverá en el segundo bloque.
Puedes usar la opción -Xlinker.
g++ -o foobar -Xlinker -start-group -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a -Xlinker -end-group
es CASI igual a
g++ -o foobar -Xlinker -start-group -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a -Xlinker -end-group
Cuidado
- ¡El orden dentro de un grupo es importante! Aquí hay un ejemplo: una biblioteca de depuración tiene una rutina de depuración, pero la biblioteca sin depuración tiene una versión débil de la misma. Debe colocar la biblioteca de depuración PRIMERO en el grupo o resolverá la versión sin depuración.
- Debe preceder cada biblioteca en la lista de grupos con -Xlinker
Si agrega -Wl,--start-group
a los indicadores del vinculador, no le importa en qué orden están o si hay dependencias circulares.
En Qt esto significa agregar:
QMAKE_LFLAGS += -Wl,--start-group
Ahorra mucho tiempo y no parece ralentizar mucho la vinculación (lo que lleva mucho menos tiempo que la compilación).
Una sugerencia rápida que me hizo tropezar: si está invocando el enlazador como "gcc" o "g ++", al usar "--start-group" y "--end-group" no pasará esas opciones a la enlazador - ni marcará un error. Solo fallará el enlace con símbolos indefinidos si el pedido de la biblioteca fue incorrecto.
Debe escribirlos como "-Wl, - start-group", etc. para decirle a GCC que pase el argumento al vinculador.
(Consulte el historial en esta respuesta para obtener un texto más elaborado, pero ahora creo que es más fácil para el lector ver líneas de comando reales).
Archivos comunes compartidos por todos los comandos a continuación
$ cat a.cpp
extern int a;
int main() {
return a;
}
$ cat b.cpp
extern int b;
int a = b;
$ cat d.cpp
int b;
Enlace a bibliotecas estáticas
$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o
$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order
El enlazador busca de izquierda a derecha y anota los símbolos no resueltos a medida que avanza. Si una biblioteca resuelve el símbolo, toma los archivos objeto de esa biblioteca para resolver el símbolo (bo fuera de libb.a en este caso).
Las dependencias de las bibliotecas estáticas entre sí funcionan de la misma manera: la biblioteca que necesita símbolos debe ser la primera, luego la biblioteca que resuelve el símbolo.
Si una biblioteca estática depende de otra biblioteca, pero la otra biblioteca depende de la biblioteca anterior, existe un ciclo. Puede resolver esto encerrando las bibliotecas dependientes cíclicamente por -(
y -)
, como -( -la -lb -)
(puede que tenga que escapar de los parens, como -/(
y -/)
). El enlazador luego busca esas bibliotecas adjuntas varias veces para asegurar que se resuelvan las dependencias de los ciclos. Alternativamente, puede especificar las bibliotecas varias veces, por lo que cada una está delante de otra: -la -lb -la
.
Enlace a bibliotecas dinámicas
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!
$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order
Es lo mismo aquí: las bibliotecas deben seguir los archivos de objetos del programa. La diferencia aquí con las bibliotecas estáticas es que no debe preocuparse por las dependencias de las bibliotecas entre sí, porque las bibliotecas dinámicas clasifican sus dependencias por sí mismas .
Algunas distribuciones recientes aparentemente tienen como valor predeterminado el uso del indicador de vinculador --as-needed
, que obliga a que los archivos objeto del programa aparezcan antes que las bibliotecas dinámicas. Si se pasa esa bandera, el enlazador no se vinculará a las bibliotecas que el ejecutable no necesita realmente (y esto lo detecta de izquierda a derecha). Mi distribución reciente de archlinux no usa este indicador por defecto, por lo que no dio un error por no seguir el orden correcto.
No es correcto omitir la dependencia de b.so
contra d.so
al crear el primero. Se le pedirá que especifique la biblioteca al vincular a
, pero a
no necesita realmente el entero b
, por lo que no debe preocuparse por las dependencias de b
.
Este es un ejemplo de las implicaciones si omite especificar las dependencias para libb.so
$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)
$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"
Si ahora observa qué dependencias tiene el binario, tenga en cuenta que el binario en sí también depende de libd
, no solo de libb
como debería. Será necesario volver a vincular el binario si, posteriormente, libb
depende de otra biblioteca, si lo haces de esta manera. Y si alguien más carga libb
utilizando dlopen
en tiempo de ejecución (piense en cargar los complementos dinámicamente), la llamada también fallará. Así que el "right"
debería ser un wrong
.