¿Por qué el orden en el que se vinculan las bibliotecas a veces provoca errores en GCC?
¿Por qué el orden en el que se vinculan las bibliotecas a veces provoca errores en GCC?
(Consulte el historial de 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 siguientes
// a depends on b, b depends on d
$ cat a.cpp
extern int a;
int main() {
return a;
}
$ cat b.cpp
extern int b;
int a = b;
$ cat d.cpp
int b;
Vinculación 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 vinculador busca de izquierda a derecha y observa los símbolos no resueltos a medida que avanza. Si una biblioteca resuelve el símbolo, se necesitan 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: primero debe estar la biblioteca que necesita símbolos y luego la biblioteca que resuelve el símbolo.
Si una biblioteca estática depende de otra biblioteca, pero la otra biblioteca nuevamente depende de la biblioteca anterior, hay un ciclo. Puede resolver esto encerrando las bibliotecas dependientes cíclicamente entre -(
y -)
, como -( -la -lb -)
(es posible que deba escapar de los pares, como -\(
y -\)
). Luego, el vinculador busca esas bibliotecas adjuntas varias veces para garantizar que se resuelvan las dependencias cíclicas. Alternativamente, puede especificar las bibliotecas varias veces, de modo que cada una esté una delante de la otra: -la -lb -la
.
Vinculación 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
Aquí ocurre lo mismo: las bibliotecas deben seguir los archivos objeto del programa. La diferencia aquí en comparación con las bibliotecas estáticas es que no es necesario preocuparse por las dependencias de las bibliotecas entre sí, porque las bibliotecas dinámicas clasifican sus dependencias por sí mismas .
Algunas distribuciones recientes aparentemente utilizan de forma predeterminada el --as-needed
indicador del vinculador, que obliga a que los archivos objeto del programa aparezcan antes que las bibliotecas dinámicas. Si se pasa ese indicador, el vinculador no vinculará a bibliotecas que en realidad no son necesarias para el ejecutable (y lo detecta de izquierda a derecha). Mi distribución reciente de archlinux no usa este indicador de forma predeterminada, por lo que no dio ningún 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
en realidad no necesita el número entero b
en sí, por lo que no debe preocuparse por b
las propias dependencias.
A continuación se muestra un ejemplo de las implicaciones si no especifica las dependencias paralibb.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, observará que el binario en sí también depende de libd
, no solo libb
como debería. Será necesario volver a vincular el binario si libb
luego depende de otra biblioteca, si lo hace de esta manera. Y si alguien más carga libb
el uso dlopen
en tiempo de ejecución (piense en cargar complementos dinámicamente), la llamada también fallará. Así que "right"
realmente debería serlo wrong
también.
El enlazador GNU ld es el llamado enlazador inteligente. Realizará un seguimiento de las funciones utilizadas por las bibliotecas estáticas anteriores, descartando permanentemente aquellas funciones que no se utilizan de 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 vínculo.
El enlazador típico de UNIX funciona de izquierda a derecha, así que coloque todas sus 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 y, al mismo tiempo, otras bibliotecas dependan de ellas. Aquí es donde se complica. Cuando se trata de referencias circulares, ¡arregle su código!
Aquí hay un ejemplo para dejar claro cómo funcionan las cosas con GCC cuando se trata de bibliotecas estáticas . Entonces supongamos que tenemos el siguiente escenario:
myprog.o
-main()
función que contiene, dependiente delibmysqlclient
libmysqlclient
- estática, por el ejemplo (preferirías la biblioteca compartida, por supuesto, ya quelibmysqlclient
es enorme); en/usr/local/lib
; y dependiente de cosas delibz
libz
(dinámica)
¿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
Si agrega -Wl,--start-group
indicadores a los del vinculador, no 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 de todos modos lleva mucho menos tiempo que la compilación).
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
Haciendo esto, no tienes que preocuparte por la secuencia correcta ya que la referencia se resolverá en el segundo bloque.