Bibliotecas dinámicas

Las bibliotecas dinámicas resuelven uno de los problemas de las bibliotecas estáticas: que el código objeto está duplicado en varios ejecutables. En una biblioteca dinámica, el código objeto compartido se mantiene en un archivo separado en todo momento (esos .so o esas .dll que puede que os suenen de algo) que se enlaza sobre la marcha por el propio sistema operativo cuando el programa es lanzado. También (específico para Linux): pegarse con ldd, ldconfig y la variable LD_LIBRARY_PATH.

En el mundo de la programación en C, las bibliotecas dinámicas representan una evolución importante respecto a las bibliotecas estáticas, y entender cómo funcionan nos permite optimizar tanto el espacio como la eficiencia de nuestros programas. Recordemos que una biblioteca estática es un archivo, comúnmente con extensión .a en Linux, que contiene código objeto empaquetado. Cuando enlazamos estáticamente una biblioteca con nuestro programa, el compilador copia literalmente los fragmentos de código objeto necesarios dentro del ejecutable final. Esto funciona bien, pero tiene algunas limitaciones importantes.

Por ejemplo, si modificamos el código fuente de la biblioteca, debemos recompilar todos los programas que la utilizan para que reflejen esos cambios. Esto puede ser tedioso y poco eficiente, especialmente si tenemos múltiples ejecutables que dependen de la misma biblioteca estática, ya que cada uno contendrá una copia del mismo código, ocupando espacio innecesario. En sistemas con recursos limitados, como microcomputadoras o dispositivos embebidos, esta duplicación puede ser un problema serio.

Las bibliotecas dinámicas, en cambio, contienen también código objeto listo para usar, pero el sistema operativo las maneja de forma diferente. Cuando ejecutamos un programa, el sistema no copia todo el código de las bibliotecas dentro del ejecutable, sino que realiza un enlazado en tiempo de ejecución. Esto significa que el código común, como la biblioteca estándar de C o funciones del sistema, no se incrusta en cada programa, sino que se mantiene en archivos compartidos ubicados en carpetas específicas del sistema, como /lib o /lib64.

Para ver qué bibliotecas dinámicas utiliza un programa, podemos usar la herramienta ldd. Por ejemplo, si inspeccionamos un ejecutable llamado contable, veremos que depende de bibliotecas como linux-vdso.so.1, libc.so.6 y ld-linux-x86-64.so.2. Estas bibliotecas están presentes en el sistema y se enlazan justo antes de ejecutar el programa, evitando la duplicación de código y facilitando actualizaciones, ya que modificar una biblioteca dinámica actualiza automáticamente todos los programas que la usan.

Este concepto no es exclusivo de Linux; en Windows, por ejemplo, las bibliotecas dinámicas tienen la extensión .dll y funcionan bajo el mismo principio. Si falta una .dll necesaria, el sistema mostrará un error al intentar ejecutar el programa.

Para crear una biblioteca dinámica en C con GCC, usamos la opción -shared. Por ejemplo, si tenemos un archivo objeto calculadora.o, podemos generar la biblioteca dinámica con:

gcc -shared -o libcalculadora.so calculadora.o

Es importante respetar las convenciones del sistema operativo: en Linux, las bibliotecas dinámicas suelen comenzar con lib y terminar con .so. En macOS, la extensión es .dylib, y en Windows .dll.

Para enlazar un programa con una biblioteca dinámica, usamos la opción -l seguida del nombre de la biblioteca sin el prefijo lib ni la extensión. Por ejemplo, para enlazar con libcalculadora.so, compilamos así:

gcc -o contable contable.c -lcalculadora

Sin embargo, si la biblioteca no está en las rutas estándar que GCC busca (como /lib, /usr/lib, /usr/local/lib), debemos indicarle dónde buscar con la opción -L. Por ejemplo, si libcalculadora.so está en una carpeta llamada lib dentro del proyecto, compilamos:

gcc -o contable contable.c -Llib -lcalculadora

Un ejemplo práctico es la biblioteca libX11.so, que se usa para crear interfaces gráficas en Linux. Si compilamos un programa que usa X11, debemos enlazar con -lX11. Como esta biblioteca está en las rutas estándar, no necesitamos especificar -L.

Al ejecutar un programa que depende de bibliotecas dinámicas, el sistema debe poder encontrarlas. Si la biblioteca no está en las rutas estándar, el programa puede fallar con un error indicando que no se encontró la biblioteca. Para solucionar esto, podemos copiar la biblioteca a una ruta estándar y ejecutar ldconfig para actualizar la caché de bibliotecas, o bien usar la variable de entorno LD_LIBRARY_PATH para añadir rutas adicionales donde el sistema buscará las bibliotecas dinámicas.

Por ejemplo, si tenemos libcalculadora.so en una carpeta lib dentro del proyecto, podemos ejecutar el programa así:

LD_LIBRARY_PATH=./lib ./contable

Esto indica al sistema que busque también en ./lib las bibliotecas necesarias.

En resumen, las bibliotecas dinámicas nos permiten compartir código entre múltiples programas sin duplicarlo, facilitando actualizaciones y ahorrando espacio. Para trabajar con ellas, debemos saber cómo compilarlas con -shared, enlazarlas con -l y -L, y asegurarnos de que el sistema operativo pueda localizarlas en tiempo de ejecución mediante rutas estándar o configurando LD_LIBRARY_PATH. Estos conocimientos son esenciales para gestionar proyectos en C de forma eficiente y profesional.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos