1. Qué es una biblioteca estática
Una biblioteca estática es, literalmente, una caja de archivos objeto. No es un formato nuevo de código: dentro hay los mismos .o que estudiamos en el tema 31, más un índice que dice qué símbolos define cada uno. Se distribuye como un archivo con nombre libNOMBRE.a (la a es de archive) junto con los encabezados .h que declaran sus funciones. «Estática» significa que el código que se toma de ella se copia dentro del ejecutable en el momento de enlazar: una vez producido el programa, la biblioteca ya no hace falta para ejecutarlo. Las dinámicas, que se cargan al ejecutar, son el tema 33.
Para trabajar con un ejemplo, armamos una biblioteca pequeña con dos módulos, uno de números y otro de texto, y un encabezado único. Los fuentes viven en el directorio de trabajo y el encabezado en inc/:
/* inc/util.h */
#ifndef UTIL_H
#define UTIL_H
#include <stddef.h>
int maximo(const int *v, size_t n);
double promedio(const int *v, size_t n);
void invertir(char *s);
void mayusculas(char *s);
#endif
/* numeros.c */
#include "util.h"
int maximo(const int *v, size_t n)
{
int m = v[0];
for (size_t i = 1; i < n; i++)
if (v[i] > m)
m = v[i];
return m;
}
double promedio(const int *v, size_t n)
{
long s = 0;
for (size_t i = 0; i < n; i++)
s += v[i];
return (double)s / (double)n;
}
/* texto.c */
#include <ctype.h>
#include <string.h>
#include "util.h"
void invertir(char *s)
{
size_t n = strlen(s);
for (size_t i = 0; i < n / 2; i++) {
char t = s[i];
s[i] = s[n - 1 - i];
s[n - 1 - i] = t;
}
}
void mayusculas(char *s)
{
for (; *s; s++)
*s = (char)toupper((unsigned char)*s);
}
/* prog.c */
#include <stdio.h>
#include "util.h"
int main(void)
{
int v[] = {4, 9, 2, 7};
char s[] = "enlace";
printf("maximo=%d promedio=%.2f\n", maximo(v, 4), promedio(v, 4));
invertir(s);
printf("%s\n", s);
return 0;
}
La biblioteca se arma con numeros.o y texto.o; el programa prog.c es el cliente. Se compilan los tres con -Iinc para que #include "util.h" encuentre el encabezado:
$ gcc -std=c17 -Wall -Wextra -Iinc -c numeros.c texto.c prog.c
2. Crearla con ar
La herramienta que empaqueta es ar, el «archivador» de la familia GNU binutils. Es anterior a tar y hoy se usa casi solo para esto:
$ mkdir libs
$ ar rcs libs/libbasico.a numeros.o texto.o
$ file libs/libbasico.a
libs/libbasico.a: current ar archive
$ ls -l libs/libbasico.a | awk '{print $5, $9}'
3476 libs/libbasico.a
$ head -c 8 libs/libbasico.a
!<arch>
$ ar t libs/libbasico.a
numeros.o
texto.o
$ ar tv libs/libbasico.a
rw-r--r-- 0/0 1544 Jan 1 00:00 1970 numeros.o
rw-r--r-- 0/0 1688 Jan 1 00:00 1970 texto.o
Las tres letras de rcs son tres operaciones juntas: r inserta los archivos (reemplazando los que ya existan con ese nombre), c crea la biblioteca sin avisar si no existía, y s escribe el índice de símbolos. El resultado es un archivo que empieza con la firma !<arch> seguida de los miembros uno tras otro, cada uno con su cabecera de permisos, dueño, tamaño y nombre. La fecha 1970 que muestra ar tv es normal: esta instalación trabaja en modo determinista, sin guardar fechas ni dueños reales, para que la misma entrada produzca siempre el mismo archivo.
Las operaciones de ar que se usan con frecuencia son pocas:
| Orden | Qué hace |
|---|---|
ar rcs lib.a a.o b.o | Crea la biblioteca o agrega/reemplaza miembros, y actualiza el índice. |
ar t lib.a / ar tv lib.a | Lista los miembros, sin o con detalles. |
ar d lib.a a.o | Borra un miembro. |
ar x lib.a [a.o] | Extrae uno o todos los miembros como archivos sueltos. |
ar p lib.a a.o | Imprime el contenido de un miembro en la salida estándar. |
ar s lib.a o ranlib lib.a | Regenera el índice, si se armó el archivo sin s. |
Las bibliotecas se mantienen con las mismas órdenes. Si se borra un miembro, desaparecen sus funciones del enlace, y si se vuelve a insertar, regresan:
$ ar d libs/libbasico.a texto.o; ar t libs/libbasico.a
numeros.o
$ gcc -o prog6 prog.o -Llibs -lbasico
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.o: in function `main':
prog.c:(.text+0x92): undefined reference to `invertir'
collect2: error: ld returned 1 exit status
$ ar rcs libs/libbasico.a texto.o; ar t libs/libbasico.a
numeros.o
texto.o
$ mkdir ext && (cd ext && ar x ../libs/libbasico.a && ls)
numeros.o texto.o
$ ar p libs/libbasico.a numeros.o | file -
/dev/stdin: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
El último comando confirma lo dicho al comienzo: un miembro es un objeto ELF reubicable, el mismo que produce gcc -c. Y el índice es lo que muestra nm con la opción -s:
$ nm -s libs/libbasico.a
Archive index:
maximo in numeros.o
promedio in numeros.o
invertir in texto.o
mayusculas in texto.o
numeros.o:
0000000000000000 T maximo
0000000000000069 T promedio
texto.o:
0000000000000000 T invertir
000000000000008d T mayusculas
U strlen
U toupper
Con el índice, el enlazador no necesita abrir cada miembro para saber qué ofrece: consulta una tabla de «símbolo → miembro». Lo que el índice no registra son los indefinidos: strlen y toupper aparecen en texto.o, pero no en el índice, y por eso tampoco hace falta que la biblioteca diga de qué otras bibliotecas depende. Esa información no existe en un .a, y es la raíz de los problemas de orden que vienen en la sección 5.
3. Usarla: -l y -L
Hay tres formas equivalentes de enlazar el programa con lo que hay en la biblioteca, y las tres producen el mismo resultado:
$ gcc -o prog1 prog.o numeros.o texto.o && ./prog1
maximo=9 promedio=5.50
ecalne
$ gcc -o prog2 prog.o libs/libbasico.a && ./prog2
maximo=9 promedio=5.50
ecalne
$ gcc -o prog3 prog.o -Llibs -lbasico && ./prog3
maximo=9 promedio=5.50
ecalne
La primera pasa los objetos sueltos; la segunda, la ruta de la biblioteca como un archivo más; la tercera es la forma habitual y tiene dos opciones:
-lNOMBREpide la bibliotecalibNOMBRE.a(olibNOMBRE.so, tema 33): se le anteponeliby se le pone la extensión, y se busca en una lista de directorios.-lbasicobuscalibbasico.a;-lmbuscalibm.aolibm.so, y es el mismo-lmdel tema 31.-Ldirectorioagrega un directorio a esa lista, antes de los del sistema. Se puede repetir.
Los directorios del sistema se ven con un modo detallado de gcc; los de -L aparecen primero, en el orden dado:
$ gcc -v -o prog3 prog.o -Llibs -lbasico 2>&1 | grep collect2 | head -1 | tr ' ' '\n' | grep '^-L'
-Llibs
-L/usr/lib/gcc/x86_64-linux-gnu/15
-L/usr/lib/gcc/x86_64-linux-gnu/15/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/15/../../../../lib
-L/lib/x86_64-linux-gnu
-L/lib/../lib
-L/usr/lib/x86_64-linux-gnu
-L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/15/../../..
-L/lib
-L/usr/lib
Además de -L, existe la variable de entorno LIBRARY_PATH, que gcc consulta al enlazar. LIBRARY_PATH=libs gcc -o l1 prog.o -lbasico funciona igual que -Llibs. No hay que confundirla con LD_LIBRARY_PATH, que es otra cosa: la usa el cargador al ejecutar (tema 33). Cuando el nombre exacto importa, -l:libbasico.a pide un archivo concreto en lugar de componer el nombre, y sirve para elegir la versión .a si hay una dinámica en el mismo directorio.
Si no se encuentra, el enlazador lo dice con un error distinto de los del tema 31, porque no es un símbolo sino un archivo:
$ gcc -o prog7 prog.o -Llibs -lnoexiste
/usr/bin/x86_64-linux-gnu-ld.bfd: cannot find -lnoexiste: No such file or directory
collect2: error: ld returned 1 exit status
Cuidado con los nombres que ya existen. Este mismo ejemplo se llamó primero libutil.a, y el enlace con -lutil y sin -L no dio cannot find, sino tres undefined reference. La razón es que glibc trae su propia libutil.a, un archivo vacío de 8 bytes (solo la firma) que se conserva por compatibilidad. El enlazador la encontró, la recorrió y no sacó nada de ella. Al elegir el nombre de una biblioteca propia conviene comprobar que no choque con ls /usr/lib/x86_64-linux-gnu/lib<nombre>.*.
4. Cómo la recorre el enlazador
Un objeto en la línea de órdenes entra siempre completo al programa. Una biblioteca, no: el enlazador la trata de manera diferente. Mantiene un conjunto de símbolos pendientes (usados y sin definición todavía) y, al llegar a una biblioteca, consulta el índice: si algún miembro define alguno de los pendientes, extrae ese miembro y lo une al programa como si fuera un objeto más. Si ninguno lo hace, la biblioteca se salta sin aportar nada. Las consecuencias se pueden comprobar. Un programa que solo llama a maximo:
/* solo_num.c */
#include <stdio.h>
#include "util.h"
int main(void)
{
int v[] = {3, 8, 5};
printf("%d\n", maximo(v, 3));
return 0;
}
$ gcc -Wl,-Map=solo.map -o solo solo_num.o -Llibs -lbasico && ./solo
8
$ sed -n '/Archive member included/,/^Discarded/p' solo.map | head -4
Archive member included to satisfy reference by file (symbol)
libs/libbasico.a(numeros.o) solo_num.o (maximo)
$ gcc -Wl,-y,maximo -o solo solo_num.o -Llibs -lbasico
/usr/bin/x86_64-linux-gnu-ld.bfd: solo_num.o: reference to maximo
/usr/bin/x86_64-linux-gnu-ld.bfd: libs/libbasico.a(numeros.o): definition of maximo
$ nm solo | grep -E ' (maximo|promedio|invertir|mayusculas)$'
00000000000011dd T maximo
0000000000001246 T promedio
$ nm prog3 | grep -E ' (maximo|promedio|invertir|mayusculas)$'
0000000000001380 T invertir
000000000000128a T maximo
000000000000140d T mayusculas
00000000000012f3 T promedio
Hay tres cosas para leer en esas salidas:
- El mapa de enlace (sección «Archive member included») lista cada miembro que entró y por qué:
numeros.ovino a satisfacer la referencia amaximodesolo_num.o.texto.ono figura: nadie lo necesitaba, y no ocupa un byte ensolo. El tema anterior lo mostró con-Wl,-y, que da la misma información para un símbolo concreto. - La unidad es el miembro, no la función.
solocontienepromedioaunque nadie la llame, porque está en el mismonumeros.oquemaximo: o entra el objeto entero, o no entra. Es la razón por la que las bibliotecas serias se escriben con una función por archivo fuente, y por la quelibc.atieneprintf.oymalloc.ocomo piezas separadas (más adelante: 2233 miembros). Quien programa una biblioteca decide con la organización de sus fuentes cuánto se arrastra. - La biblioteca no se ve en el ejecutable.
nmdesolomuestra los símbolos como cualquier otro, sin rastro del.a: ya es código del programa.
El recorrido tiene además una regla de prioridad que sale de lo anterior: lo que está en un objeto de la línea de órdenes gana a la biblioteca, porque el símbolo ya no está pendiente cuando se llega a ella. Solo gana si se cubre todo lo que ofrece el miembro. Un caso de cada clase:
/* mi_max.c, primera versión: solo maximo */
$ gcc -o prog9 prog.o mi_max.o -Llibs -lbasico
/usr/bin/x86_64-linux-gnu-ld.bfd: libs/libbasico.a(numeros.o): in function `maximo':
numeros.c:(.text+0x0): multiple definition of `maximo'; mi_max.o:mi_max.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
/* mi_max.c, segunda versión: maximo y promedio */
$ gcc -o prog9 prog.o mi_max.o -Llibs -lbasico && ./prog9
maximo=-1 promedio=-1.00
ecalne
En el primer caso, promedio sigue pendiente después del objeto y obliga a extraer numeros.o, que trae además maximo: dos definiciones fuertes, multiple definition. En el segundo, ya no queda nada pendiente de ese miembro, no se extrae, y se usa la versión propia sin error. Así es como un programa puede reemplazar a una función de una biblioteca estática, y también como se arma un parche o un doble de pruebas. Para que ocurra, el reemplazo debe cubrir todos los símbolos del miembro; cuando la biblioteca tiene una función por archivo, es lo normal. Otra variante: dos miembros de la misma biblioteca que definen lo mismo no son error, sino que gana el primero que el índice encuentre. Con ar rcs libs/libdup.a numeros2.o numeros.o, donde numeros2.o define maximo y promedio devolviendo 0, el programa imprime maximo=0 promedio=0.00.
5. El orden de la línea de órdenes
Si el enlazador recorre cada biblioteca una sola vez, en el orden en que aparece, y solo extrae lo que en ese momento está pendiente, entonces importa qué hay antes y qué hay después. Es la regla que más errores cuesta y se ve con una línea cambiada de lugar:
$ gcc -o prog4 libs/libbasico.a prog.o
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.o: in function `main':
prog.c:(.text+0x53): undefined reference to `promedio'
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.c:(.text+0x69): undefined reference to `maximo'
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.c:(.text+0x92): undefined reference to `invertir'
collect2: error: ld returned 1 exit status
$ gcc -o prog5 -Llibs -lbasico prog.o
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.o: in function `main':
prog.c:(.text+0x53): undefined reference to `promedio'
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.c:(.text+0x69): undefined reference to `maximo'
/usr/bin/x86_64-linux-gnu-ld.bfd: prog.c:(.text+0x92): undefined reference to `invertir'
collect2: error: ld returned 1 exit status
Las dos líneas fallan igual: los tres errores nombran los símbolos que usa main. Es el mismo diagnóstico del tema 31 (un símbolo sin definición), pero la causa no es que falte código, sino que la biblioteca ya pasó. La regla se resume en una frase:
Regla de la línea de órdenes. Los objetos primero, después las bibliotecas, y cada biblioteca después de todo lo que la usa. Si A necesita a B, el orden es A B, nunca B A.
Con una biblioteca estándar se cumple casi sin pensar, porque -lm y las demás van al final. Con un Makefile (tema 35) hay que escribir las bibliotecas en una variable aparte, al final de la orden de enlace.
6. Dependencias circulares
La regla anterior supone que las dependencias van en un solo sentido. Cuando dos bibliotecas se necesitan mutuamente, o cuando una biblioteca necesita algo que está en una anterior, ningún orden basta. Se puede armar un caso mínimo. La biblioteca liba.a tiene a.o y a2.o, y libb.a tiene b.o; a_fun llama a b_fun, y b_fun llama a a2_fun, otro miembro de liba.a:
/* a.c */
int b_fun(int);
int a_fun(int n) { return n <= 0 ? 0 : 1 + b_fun(n - 1); }
/* a2.c */
int a2_fun(int n) { return n * 10; }
/* b.c */
int a2_fun(int);
int b_fun(int n) { return a2_fun(n) + 1; }
/* circ.c */
#include <stdio.h>
int a_fun(int);
int main(void) { printf("%d\n", a_fun(5)); return 0; }
$ gcc -o c1 circ.o -Llibs -la -lb
/usr/bin/x86_64-linux-gnu-ld.bfd: libs/libb.a(b.o): in function `b_fun':
b.c:(.text+0x15): undefined reference to `a2_fun'
collect2: error: ld returned 1 exit status
$ gcc -o c2 circ.o -Llibs -lb -la
/usr/bin/x86_64-linux-gnu-ld.bfd: libs/liba.a(a.o): in function `a_fun':
a.c:(.text+0x1e): undefined reference to `b_fun'
collect2: error: ld returned 1 exit status
$ gcc -o c3 circ.o -Llibs -la -lb -la && ./c3
42
$ gcc -o c4 circ.o -Llibs -Wl,--start-group -la -lb -Wl,--end-group && ./c4
42
Con -la -lb, se extrae a.o (por a_fun), que deja pendiente b_fun; libb.a lo resuelve con b.o, que deja pendiente a2_fun, pero liba.a ya pasó. Con -lb -la la falla es la inversa: cuando se recorre libb.a, nadie pidió b_fun todavía. Hay dos soluciones. La primera es repetir la biblioteca: -la -lb -la permite que la segunda pasada de liba.a resuelva a2_fun. La segunda es agrupar con -Wl,--start-group … -Wl,--end-group, que ordena al enlazador recorrer repetidamente las bibliotecas del grupo hasta que no cambie nada. Es más cómoda y más lenta, y por eso se usa solo cuando el ciclo es real. El resultado de las dos, 42, es el valor de a_fun(5): 1 + (a2_fun(4) + 1) = 1 + 41.
Si se puede, la solución de fondo es evitar el ciclo: reorganizar las bibliotecas para que la dependencia vaya en un solo sentido, o fusionarlas en una. Los ciclos de este tipo son, en la práctica, un olor de diseño.
7. Forzar todo: --whole-archive
A veces conviene lo contrario del recorrido selectivo: incorporar todos los miembros, se necesiten o no. La situación típica es el código que se registra solo, por ejemplo un módulo cuyas funciones nadie llama directamente pero que, al cargarse, se anota en una tabla; o el caso de armar una biblioteca dinámica a partir de una estática (tema 33). Para eso:
$ ar rcs libs/libbasico.a extra.o && ar t libs/libbasico.a
numeros.o
texto.o
extra.o
$ gcc -o n1 prog.o -Llibs -lbasico
$ nm n1 | grep -E ' (maximo|promedio|invertir|mayusculas|registrar)$'
0000000000001380 T invertir
000000000000128a T maximo
000000000000140d T mayusculas
00000000000012f3 T promedio
$ gcc -o n2 prog.o -Llibs -Wl,--whole-archive -lbasico -Wl,--no-whole-archive
$ nm n2 | grep -E ' (maximo|promedio|invertir|mayusculas|registrar)$'
0000000000001380 T invertir
000000000000128a T maximo
000000000000140d T mayusculas
00000000000012f3 T promedio
000000000000144c T registrar
$ size n1 n2 | awk '{print $1, $6}'
text filename
2561 n1
2638 n2
Sin la opción (n1), registrar no entra: nadie la pide. Con --whole-archive (n2) entra el miembro extra.o completo, y el ejecutable crece 77 bytes de código. --no-whole-archive restituye el comportamiento normal para las bibliotecas siguientes, y las dos opciones se escriben alrededor de las bibliotecas a las que se aplican.
8. Enlace totalmente estático
Hasta aquí, la biblioteca estática era nuestra, pero el programa seguía siendo dinámico respecto de la biblioteca de C: printf se resolvía en tiempo de ejecución contra libc.so.6 (tema 31, sección 7). La opción -static le pide a gcc que enlace todo estáticamente, también libc:
$ gcc -o dyn prog.o numeros.o texto.o
$ gcc -static -o est prog.o numeros.o texto.o && ./est
maximo=9 promedio=5.50
ecalne
$ ls -l dyn est | awk '{print $5, $9}'
16328 dyn
821712 est
$ file dyn est | sed 's/, BuildID.*//; s/, for GNU.*//'
dyn: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2
est: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked
$ ldd dyn; ldd est
linux-vdso.so.1 (0x00007ffcc3eac000)
libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007c96afa00000)
/lib64/ld-linux-x86-64.so.2 (0x00007c96afde1000)
not a dynamic executable
$ size dyn est
text data bss dec hex filename
2561 632 8 3201 c81 dyn
701831 22784 22560 747175 b66a7 est
El resultado es un ejecutable autosuficiente: file lo declara statically linked, ldd responde not a dynamic executable (no tiene dependencias que resolver, ni intérprete ld.so), y pesa 821 712 bytes contra 16 328, porque lleva dentro las piezas de libc que necesita. Las bibliotecas estáticas de C del sistema están en el mismo lugar que las dinámicas, y se recorren igual que la nuestra:
$ ls /usr/lib/x86_64-linux-gnu/libc.a /usr/lib/x86_64-linux-gnu/libm.a
/usr/lib/x86_64-linux-gnu/libc.a
/usr/lib/x86_64-linux-gnu/libm.a
$ ar t /usr/lib/x86_64-linux-gnu/libc.a | wc -l
2233
$ ar t /usr/lib/x86_64-linux-gnu/libc.a | grep -E '^(printf|puts|malloc)\.o$'
printf.o
malloc.o
La biblioteca de C estática es un .a de 2233 miembros, uno por función en muchos casos: de allí el tamaño razonable de est, pues solo entran las piezas que hacen falta (más el código de arranque y todo lo que ese código arrastra). Hay variantes parciales que permiten mezclar. -Wl,-Bstatic hace que las bibliotecas que siguen se busquen solo como .a, y -Wl,-Bdynamic vuelve al modo normal:
$ gcc -o prog12 prog.o -Wl,-Bstatic -Llibs -lbasico -Wl,-Bdynamic && ./prog12
maximo=9 promedio=5.50
ecalne
$ gcc -o prog11 prog.o -Llibs -l:libbasico.a && ./prog11
maximo=9 promedio=5.50
ecalne
Con -Bstatic, solo se busca libbasico.a; es útil cuando en el mismo directorio hay una versión dinámica y se quiere forzar la estática. La opción -l:libbasico.a de la sección 3 logra el mismo efecto con un nombre exacto. Decidir entre enlazar todo estático o no es un compromiso:
| Estático | Dinámico (tema 33) | |
|---|---|---|
| Despliegue | Un solo archivo; funciona en cualquier máquina con el mismo núcleo. | Necesita las bibliotecas correctas instaladas. |
| Tamaño del ejecutable | Mayor: contiene el código de las bibliotecas. | Menor. |
| Memoria | Cada programa tiene su copia del código. | El código de la biblioteca se comparte entre procesos. |
| Arranque | Más rápido: no hay nada que cargar ni enlazar. | El cargador debe buscar y reubicar. |
| Actualizaciones | Un arreglo en la biblioteca exige reenlazar cada programa. | Se actualiza la biblioteca y todos los programas la aprovechan. |
| Dependencias | Ninguna en tiempo de ejecución. | ldd las lista; pueden faltar o ser de otra versión. |
El enlace totalmente estático se elige en contenedores mínimos (una imagen que contiene solo el ejecutable), en herramientas de recuperación del sistema, y cuando se distribuye un binario a máquinas que no se controlan. Tiene un costo de seguridad: los parches de libc no llegan al programa si nadie lo reconstruye. Algunas funciones de glibc (por ejemplo las de resolución de nombres) además cargan módulos dinámicos incluso en programas estáticos, y el enlazador avisa; es otro motivo para probar el binario estático en el entorno final.
9. Errores frecuentes
| Error | Qué pasa | Corrección |
|---|---|---|
Poner -lbasico antes de prog.o | undefined reference a todo lo de la biblioteca. | Objetos primero, bibliotecas al final. |
Olvidar -L para una biblioteca fuera de las rutas del sistema | cannot find -lNOMBRE. | -Ldirectorio o pasar la ruta completa. |
| Nombre de biblioteca que choca con una del sistema | Se enlaza la del sistema (vacía o distinta) y faltan símbolos, como pasa con libutil.a. | Elegir otro nombre; -L va antes que los directorios del sistema. |
Armar el .a sin índice (sin s ni ranlib) con un ar que no lo agrega solo | Algunos enlazadores se quejan de que el archivo no tiene índice. | ar rcs siempre. |
Olvidarse de recompilar el .a después de cambiar un fuente | El programa sigue con la versión vieja de la función. | Regenerar con ar rcs en cada cambio (el Makefile del tema 35 lo automatiza). |
| Esperar que se incorpore toda la biblioteca | Solo entran los miembros que resuelven pendientes. | --whole-archive cuando de verdad se necesite todo. |
Varias funciones en un mismo .c de la biblioteca | Una llamada arrastra todas las del archivo. | Una función por archivo, o -ffunction-sections con --gc-sections (tema 31). |
| Redefinir una función de la biblioteca sin cubrir todo su miembro | multiple definition. | Definir todos los símbolos del miembro, o usar un símbolo débil. |
| Dos bibliotecas que se necesitan mutuamente | Ningún orden simple funciona. | -Wl,--start-group … --end-group, o rediseñar. |
Confundir LIBRARY_PATH con LD_LIBRARY_PATH | La primera es del enlace; la segunda, de la ejecución. | Cada una en su momento. |
Enlazar con -static sin tener libc.a | Error de enlace por falta de la biblioteca estática. | Instalar el paquete de desarrollo estático de glibc. |
| Entregar un binario estático sin plan de actualización | Los parches de seguridad de libc no llegan. | Reconstruir y redistribuir ante cada aviso. |
10. Resumen
- Una biblioteca estática es un archivo
libNOMBRE.acon archivos objeto y un índice de símbolos. Se crea conar rcs, se inspecciona conar tynm -sy se mantiene conar d,ar xyar r. -lNOMBREbuscalibNOMBRE.a(o.so) en los directorios de-L, deLIBRARY_PATHy del sistema;-l:archivopide un nombre exacto. Se puede pasar también la ruta del.acomo un archivo más.- El enlazador recorre cada biblioteca una vez, y extrae solo los miembros que definen símbolos pendientes. Lo extraído se copia al ejecutable; la biblioteca ya no hace falta al ejecutar.
- La unidad de extracción es el miembro, no la función: se escribe una función por archivo fuente. El mapa de enlace (
Archive member included) y-Wl,-ymuestran qué entró y por qué. - Un objeto de la línea gana a la biblioteca, si cubre todos los símbolos del miembro; si no, hay
multiple definition. Entre miembros de una biblioteca, gana el primero. - El orden importa: objetos primero y cada biblioteca después de lo que la usa. Los ciclos se resuelven repitiendo la biblioteca o con
-Wl,--start-group/--end-group. -Wl,--whole-archivefuerza todos los miembros;-Wl,-Bstaticy-Bdynamiceligen el tipo de biblioteca para las que siguen.gcc -staticenlaza tambiénlibc.a(2233 miembros): el ejecutable es autosuficiente (ldd: not a dynamic executable) pero pesa mucho más y no recibe las actualizaciones de la biblioteca.
11. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué contiene un archivo libbasico.a, y qué significa que sea «estática»?
Una colección de archivos objeto (miembros) más un índice de símbolos, dentro del formato de ar. «Estática» significa que el código que se toma de ella se copia al ejecutable al enlazar; después, el programa no depende de la biblioteca para ejecutarse.
2. ¿Qué hacen las letras de ar rcs libs/libbasico.a numeros.o texto.o?
r inserta los archivos, reemplazando los de igual nombre; c crea la biblioteca sin avisar si no existía; s escribe el índice de símbolos, que el enlazador consulta para saber qué miembro define cada símbolo.
3. ¿Cómo se ve qué miembros tiene una biblioteca y qué define cada uno?
ar t lista los miembros (con v, además tamaño y permisos); nm -s muestra el índice de símbolos y, debajo, los símbolos de cada miembro, con los indefinidos como U.
4. ¿Qué buscan -lbasico y -Llibs?
-lbasico pide la biblioteca libbasico.a (o libbasico.so), y -Llibs agrega el directorio libs a la lista de búsqueda, antes de los directorios del sistema. Sin -L, el enlazador solo mira los directorios estándar y LIBRARY_PATH, y si no la encuentra dice cannot find -lbasico.
5. El programa solo, que solo llama a maximo, contiene también promedio pero no invertir. ¿Por qué?
Porque el enlazador extrae miembros enteros. maximo y promedio están en numeros.o, así que entran juntas; texto.o no resuelve ningún símbolo pendiente y no se incorpora. Por eso las bibliotecas se escriben con una función por archivo fuente.
6. ¿Cómo se comprueba qué miembros de la biblioteca entraron en un enlace, y por qué?
Con el mapa de enlace (-Wl,-Map=archivo), en la sección «Archive member included», que lista cada miembro y el símbolo y archivo que lo motivaron. Para un símbolo concreto sirve -Wl,-y,nombre.
7. ¿Por qué gcc -o prog libs/libbasico.a prog.o falla y gcc -o prog prog.o libs/libbasico.a funciona?
El enlazador recorre la biblioteca una vez, cuando la encuentra en la línea. En el primer caso, no hay símbolos pendientes en ese momento (prog.o aún no se leyó), así que no extrae nada; después prog.o deja pendientes maximo, promedio e invertir, y la biblioteca ya pasó. En el segundo, prog.o está antes y los pendientes existen al llegar a la biblioteca.
8. ¿Cuál es la regla general del orden y cómo se aplica con -lm?
Los objetos primero y cada biblioteca después de todo lo que la usa. Por eso -lm (y las demás -l) van al final de la línea de gcc, después de los archivos .c y .o.
9. liba.a necesita símbolos de libb.a y viceversa. ¿Cómo se enlaza?
Repitiendo una biblioteca (-la -lb -la) para que una segunda pasada resuelva lo que quedó pendiente, o encerrándolas en -Wl,--start-group -la -lb -Wl,--end-group, que hace que el enlazador las recorra repetidamente hasta que no haya cambios. La solución de fondo es rediseñar para eliminar el ciclo.
10. ¿Cuándo gana un objeto de la línea de órdenes a una función de la biblioteca, y cuándo da multiple definition?
Gana si el objeto define todos los símbolos que pedirían extraer el miembro de la biblioteca: entonces no queda nada pendiente de ese miembro y no se incorpora. Si el objeto redefine solo una de las funciones del miembro, queda otra pendiente, el miembro se extrae completo, y la función repetida produce multiple definition.
11. ¿Para qué sirve --whole-archive?
Para incorporar todos los miembros de una biblioteca aunque nadie los pida, por ejemplo cuando un módulo se registra solo al cargarse o cuando se arma una biblioteca dinámica a partir de una estática. Se cierra con --no-whole-archive para que las bibliotecas siguientes vuelvan al comportamiento normal.
12. ¿Qué cambia en el ejecutable al compilar con -static?
Se enlaza también libc.a (y las demás), de modo que el binario lleva dentro todo el código que usa. No tiene dependencias (ldd dice not a dynamic executable) ni intérprete ld.so, y es mucho mayor: 821 712 bytes contra 16 328 en este ejemplo.
13. ¿Qué ventajas y qué costos tiene distribuir un binario estático?
Ventajas: un solo archivo que funciona sin instalar bibliotecas, arranque más rápido, nada que pueda faltar o tener otra versión. Costos: tamaño mayor, el código de las bibliotecas no se comparte entre procesos, y las actualizaciones (sobre todo de seguridad) de las bibliotecas no llegan al programa hasta que se vuelva a enlazar.
14. Un enlace con -lutil falla con undefined reference y no con cannot find -lutil, aunque la biblioteca propia no está en el camino. ¿Qué pasó?
El enlazador sí encontró un libutil.a: el que trae glibc en /usr/lib/x86_64-linux-gnu, un archivo vacío por compatibilidad. Lo recorrió, no sacó nada, y los símbolos siguieron sin definir. Se evita eligiendo nombres que no existan en el sistema y pasando -L con la biblioteca propia.
Para practicar
- Arma tu propia biblioteca
libgeo.acon cuatro funciones de geometría, una por archivo.c, y un programa que use dos. Con el mapa de enlace, comprueba qué miembros entraron. - Reúne las cuatro funciones en un solo
.c, reconstruye la biblioteca y compara el tamaño del ejecutable consize. ¿Qué cambió, y por qué? - Prueba en tu máquina las dos líneas de enlace con el orden cambiado y anota los mensajes. ¿Cambia el mensaje si en lugar de
-lgeoescribes la ruta del.a? - Escribe un objeto que reemplace una función de tu biblioteca. Pruébalo con la versión que redefine solo esa función y con la que redefine todas las del miembro. ¿Qué mensaje da la primera?
- Arma dos bibliotecas
liba.aylibb.acon una dependencia cruzada entre miembros distintos, y enlázalas de cuatro maneras (dos órdenes simples, repetida, con grupo). ¿Cuál es la más barata en tiempo de enlace? - Crea un directorio con una
libgeo.ay unalibgeo.soy enlaza con-lgeo, con-Wl,-Bstatic -lgeoy con-l:libgeo.a. Usalddpara ver cuál se eligió en cada caso (la dinámica se arma en el tema 33). - Enlaza un programa que use
printfcon y sin-static, y compara consizeynm | wc -lcuántos símbolos trae cada uno. - Con
ar t /usr/lib/x86_64-linux-gnu/libc.a | head -50, lista los primeros miembros delibc.ay busca cuáles componenprintfusandonm -Asobre los miembros extraídos conar x.