Guía Hardware

Bibliotecas de software: qué son y cómo funcionan

Actualizado a: 19 de enero de 2024

El software que cada día ejecutamos necesita de unas bibliotecas para funcionar, tanto las aplicaciones de usuario como el propio sistema operativo hace uso de ellas. Pero ¿sabes realmente qué son? ¿qué tipos hay? ¿cómo funcionan? Pues aquí te vamos a resolver todas tus dudas…

También te puede interesar:

¿Qué es una biblioteca de software?

cómo abrir archivos DLL

Una biblioteca (library) es una colección de recursos no volátiles utilizados por programas de ordenador, a menudo para el desarrollo de software. Estos recursos pueden incluir datos de configuración, documentación, datos de ayuda, plantillas de mensajes, código preescrito y subrutinas, clases, valores o especificaciones de tipo.

Muchos usan la palabra librería para designar a estos recursos, pero es erróneo, ya que la traducción del inglés no es esa. Library es biblioteca, no librería…

Una biblioteca también es una colección de implementaciones de comportamiento escritas en términos de un lenguaje que tiene una interfaz bien definida mediante la cual se invoca el comportamiento. Por ejemplo, las personas que desean escribir un programa de alto nivel pueden utilizar una biblioteca para realizar llamadas al sistema en lugar de implementar esas llamadas repetidamente. Además, también se pueden emplear para definir funciones básicas que se usan repetitivamente y así evitar escribir el código, simplemente se invocan. Es decir, son una especie de bloques de construcción para el desarrollo de software que se pueden utilizar sin necesidad de escribir todo desde cero.

El código de la biblioteca está organizado de tal manera que puede ser utilizado por múltiples programas que no tienen conexión entre sí, mientras que el código que forma parte de un programa está organizado para ser utilizado solo dentro de ese programa. Esta distinción puede adquirir una noción jerárquica cuando un programa se vuelve grande, como un programa de varios millones de líneas. En ese caso, puede haber bibliotecas internas que son reutilizadas por subsecciones independientes del programa grande. La característica distintiva es que una biblioteca está organizada con el propósito de ser reutilizada por programas o subprogramas independientes, y el usuario solo necesita conocer la interfaz y no los detalles internos de la biblioteca.

El valor de una biblioteca radica en la reutilización de elementos de programas estandarizados. Cuando un programa invoca una biblioteca, obtiene el comportamiento implementado en esa biblioteca sin tener que implementar ese comportamiento por sí mismo. Las bibliotecas fomentan el intercambio de código de manera modular y facilitan la distribución del código.

La mayoría de los lenguajes compilados tienen bibliotecas estándar, aunque los programadores también pueden crear sus propias bibliotecas personalizadas. Muchos lenguajes compilados también tienen bibliotecas, como es el caso de Python, entre otros.

Cómo funciona una biblioteca (ejemplos)

Por ejemplo, en un programa en C para mostrar un mensaje en pantalla (Hola Mundo), como el siguiente, se necesita hacer uso de una función denominada printf para que muestre un mensaje en pantalla. Pero esa función está definida en la biblioteca stdio.h, por eso se invoca al principio del código fuente, y luego simplemente se introduce la palabra reservada para esta función y ya funcionaría, sin escribir el código desde cero:

#include <stdio.h>

int main() {
    printf("Hola Mundo\n");
    return 0;
}

Este programa utiliza la función printf() de la biblioteca estándar stdio.h para mostrar el mensaje «Hola Mundo» en la consola. Al ejecutar este programa, verás el mensaje impreso en la salida de la consola. Por ejemplo, en GNU C library, el código de esta biblioteca sería el que se muestra en este enlace.

Por otro lado, ocurre algo parecido cuando se hace uso de llamadas al sistema. Por este motivo, un programa escrito para un sistema operativo no es compatible con otro, aunque el equipo use la misma arquitectura de hardware. El motivo es que las syscalls son diferentes. Por ejemplo, en Linux, sería:

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    const char* filename = "ejemplo.txt";
    int fd;

    // Usamos la llamada al sistema open() para abrir el archivo
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("Error al abrir el archivo");
        return 1;
    }

    // Aquí puedes realizar operaciones con el archivo abierto...

    // Cerramos el archivo después de usarlo
    if (close(fd) == -1) {
        perror("Error al cerrar el archivo");
        return 1;
    }

    return 0;
}

Este programa abre el archivo «ejemplo.txt» en modo de solo lectura utilizando la llamada al sistema open(). Si la llamada al sistema tiene éxito, open() devuelve un descriptor de archivo (fd), que es un número entero que representa el archivo abierto. Luego, el programa puede realizar operaciones con el archivo. Finalmente, se cierra el archivo utilizando la llamada al sistema close(). Recuerda que este es solo un ejemplo básico para demostrar cómo abrir un archivo usando una llamada al sistema en C. En una aplicación real, también deberías manejar posibles errores y otros aspectos relacionados con el manejo de archivos.

En el caso de Linux, la biblioteca de syscalls o llamadas al sistema, lo puedes ver en el siguiente enlace. Para Windows u otros sistemas operativos sería diferente, pero si son de código cerrado, no se puede ver…

ABI

ABI (Interfaz binaria de aplicación o Application Binary Interface) en inglés. Es un conjunto de convenciones y reglas que especifican cómo los programas de aplicaciones interactúan con el sistema operativo y otros programas a nivel binario (es decir, en el nivel de lenguaje de máquina y código objeto).

La ABI es esencial para lograr la interoperabilidad y compatibilidad entre diferentes componentes de software, como bibliotecas compartidas, programas compilados y el sistema operativo mismo. Define cómo se deben organizar y acceder a los datos, cómo se llaman a las funciones del sistema y cómo se manejan los errores, entre otros aspectos.

Algunos de los elementos que una ABI puede especificar incluyen:

  • Convenciones de llamada: cómo se pasan los argumentos a las funciones y cómo se devuelven los resultados.
  • Formato de datos: cómo se organizan y representan los datos en memoria, como enteros, punteros y estructuras.
  • Registros de uso especial: qué registros de la CPU se deben preservar y cuáles se pueden modificar durante las llamadas a funciones.
  • Manejo de excepciones: cómo se manejan las excepciones y errores durante la ejecución del programa.
  • Formato del código objeto y bibliotecas compartidas: cómo se empaquetan y organizan los archivos ejecutables y las bibliotecas compartidas.

La existencia de una ABI bien definida y estandarizada es fundamental para que los programas desarrollados en diferentes lenguajes y compiladores puedan comunicarse y funcionar juntos en un sistema operativo específico. Además, también permite que las bibliotecas escritas por diferentes desarrolladores o empresas sean utilizadas por otros programas sin problemas de compatibilidad.

Las ABIs pueden variar entre diferentes arquitecturas de CPU y sistemas operativos. Por ejemplo, la ABI utilizada en sistemas x86 puede ser diferente de la utilizada en sistemas ARM o en sistemas operativos Windows y Linux. Por lo tanto, es importante tener en cuenta la ABI al compilar y enlazar programas para garantizar su correcto funcionamiento en el sistema objetivo.

API

API (Application Programming Interface), en programación, es un conjunto de reglas y protocolos que permite que diferentes componentes de software se comuniquen entre sí. Es como un contrato que define cómo los distintos programas o servicios pueden interactuar y compartir datos.

Una API proporciona una serie de funciones, procedimientos, métodos o puntos de acceso que permiten a los desarrolladores utilizar las capacidades y recursos de un software o servicio sin necesidad de conocer todos los detalles internos de cómo funciona. En lugar de tener que programar todo desde cero, los desarrolladores pueden utilizar una API existente para realizar diversas tareas.

Las APIs juegan un papel crucial en el desarrollo de software, ya que permiten la integración, reutilización de código y la creación de aplicaciones complejas utilizando componentes previamente construidos. También ayudan a mantener una separación clara entre los componentes, lo que permite que los desarrolladores trabajen en paralelo y evita que detalles internos de un componente afecten a otros.

Diferencias API vs biblioteca

Entonces… ¿es lo mismo una biblioteca que una API? Una biblioteca es una colección de código que realiza una tarea (o un conjunto de tareas relacionadas). Una biblioteca simplemente parece un conjunto de código. Una API es la interfaz que utilizas para interactuar con otro sistema (que puede ser una biblioteca). Una API típicamente se ve como una lista de métodos y propiedades.

Un poco de historia sobre las bibliotecas de software

system 360

La idea de una biblioteca se remonta a los primeros ordenadores creadas por Charles Babbage. En un artículo de 1888 sobre su Máquina Analítica, se sugirió que las operaciones informáticas podrían registrarse en tarjetas perforadas separadas del input numérico. Si estas tarjetas perforadas con las operaciones se guardaran para su reutilización, entonces «poco a poco, la máquina tendría su propia biblioteca».

En 1947, Goldstine y von Neumann especularon que sería útil crear una «biblioteca» de subrutinas para su trabajo en la máquina IAS, un ordenador temprano que aún no estaba operativo en ese momento. Imaginaron una biblioteca física de grabaciones magnéticas de alambre, donde cada alambre almacenaría código reutilizable.

Inspirados por von Neumann, Wilkes y su equipo construyeron EDSAC. Un archivador de cinta perforada contenía la biblioteca de subrutinas para este ordenador. Los programas para EDSAC consistían en un programa principal y una secuencia de subrutinas copiadas de la biblioteca de subrutinas. En 1951, el equipo publicó el primer libro de texto sobre programación, «La preparación de programas para un ordenador electrónico digital», que detallaba la creación y el propósito de la biblioteca.

Ver los ordenadores más importantes de la historia

COBOL y FORTRAN también fueron dos grandes contribuyentes de lo que actualmente se conoce como una biblioteca moderna. Estos lenguajes compilados se podían enlazar con un linker para incluir suprogramas. Luego llegaría IBM System/360, otro de los ordenadores que marcaron un hito y que también contribuyeron al avance de lo que es actualmente la informática, con algunas innovaciones, entre ellas el uso de bibliotecas era común…

Tipos de bibliotecas

registrar DLL en CMD

Existen varios tipos de bibliotecas que deberías conocer, aunque generalmente hay dos que son las más importantes: compartidas y estáticas. No obstante, vamos a ver todos los tipos:

Bibliotecas estáticas

Una biblioteca estática (static library) o biblioteca enlazada estáticamente es un conjunto de rutinas, funciones externas y variables que se resuelven en el código del programa que lo llama en tiempo de compilación y se copian en una aplicación objetivo por un compilador, enlazador o vinculador, produciendo un archivo objeto y un ejecutable independiente. Este ejecutable y el proceso de compilarlo se conocen como una construcción estática del programa.

En sistemas tipo Unix (macOS, Linux, Solaris, BSD,…) se emplean extensiones de fichero .a. Mientras que en el caso de Windows tenemos archivos .LIB.

Históricamente, las bibliotecas solo podían ser estáticas. Las bibliotecas estáticas se combinan con otras bibliotecas estáticas y archivos objeto durante la compilación/enlazado para formar un solo ejecutable, o se cargan en tiempo de ejecución en el espacio de direcciones de su ejecutable correspondiente con un desplazamiento de memoria estático determinado en tiempo de compilación/enlazado.

Hay varias ventajas en vincular bibliotecas estáticamente con un ejecutable en lugar de enlazarlas dinámicamente. La ventaja más significativa es que la aplicación puede estar segura de que todas sus bibliotecas están presentes y que son de la versión correcta. Esto evita problemas de dependencia, conocidos coloquialmente como «DLL Hell» en Windows o más generalmente como «dependency hell» en otros sistemas operativos. El enlace estático también puede permitir que la aplicación se contenga en un solo archivo ejecutable, lo que simplifica la distribución e instalación.

Con el enlace estático, es suficiente incluir aquellas partes de la biblioteca que son directa o indirectamente referenciadas por el ejecutable objetivo (o biblioteca objetivo). Con bibliotecas dinámicas, se carga toda la biblioteca, ya que no se sabe de antemano qué funciones serán invocadas por las aplicaciones. Si esta ventaja es significativa en la práctica depende de la estructura de la biblioteca.

En el enlace estático, el tamaño del ejecutable es mayor que en el enlace dinámico, ya que el código de la biblioteca se almacena dentro del ejecutable en lugar de en archivos separados. Pero si los archivos de la biblioteca se cuentan como parte de la aplicación, entonces el tamaño total será similar, o incluso menor si el compilador elimina los símbolos no utilizados.

Bibliotecas compartidas

Una biblioteca compartida (shared library), o biblioteca dinámica, es un archivo que está diseñado para ser utilizado por archivos ejecutables y otros archivos de objetos compartidos. Los módulos que un programa necesita se cargan desde archivos de objetos compartidos individuales en la memoria durante el tiempo de carga o ejecución, en lugar de ser copiados por un enlazador para crear un único archivo ejecutable monolítico.

En sistemas operativos Unix (MacOS, Linux, Solaris, BSD,…) se usan extensiones .so para las dinámicas. Y suelen estar almacenadas en directorios como /lib. En Windows, en cambio, emplean archivos .DLL y se almacenan en varias carpetas de C:>\Windows, como System, System32, etc.

Estas bibliotecas compartidas pueden ser enlazadas estáticamente durante la compilación, lo que resuelve las referencias a los módulos y asigna memoria cuando se crea el archivo ejecutable. Sin embargo, en muchos casos, el enlace de estas bibliotecas se pospone hasta que se cargan y utilizan.

La mayoría de los sistemas operativos modernos permiten que las bibliotecas compartidas tengan el mismo formato que los archivos ejecutables. Esto presenta ventajas como tener un único cargador para ambos, lo que simplifica la complejidad, y permite que los ejecutables también se utilicen como bibliotecas compartidas si tienen una tabla de símbolos. Los formatos típicos de ejecutables y bibliotecas compartidas son ELF y Mach-O en sistemas Unix, y PE en Windows.

El uso de memoria compartida permite que el código de la biblioteca sea compartido en la memoria por varios procesos y en el disco. Esto puede mejorar la eficiencia, especialmente cuando se utiliza memoria virtual. Por otro lado, diferentes versiones de bibliotecas compartidas pueden causar problemas si no están adecuadamente identificadas y referenciadas por los programas, lo que se conoce como el «infierno de dependencias» o «infierno de DLL» que ya comenté antes.

El enlace dinámico, que se originó en el sistema operativo Multics y en el MTS, es un enlace que se realiza durante el tiempo de carga o ejecución del programa. Esto permite que se realice un mínimo de trabajo en la creación del archivo ejecutable, y la mayor parte del enlace se lleva a cabo cuando se carga o ejecuta la aplicación. La optimización conocida como pre-enlace o pre-enlazado puede calcular direcciones de carga probables para las bibliotecas compartidas antes de que se necesiten, acelerando así el proceso de enlace dinámico. Sin embargo, esta técnica también presenta desventajas, como el tiempo requerido para calcular estas direcciones cada vez que cambian las bibliotecas compartidas y la necesidad de suficiente espacio de dirección virtual para su uso.

Otras

También existen otros tipos de bibliotecas, como las objet libraries, que están orientadas a objetos, como su propio nombre indica. También tenemos las bibliotecas de clases, o class libraries, que en este caso son similares a las anteriores, pero definen acciones y características que involucran a objetos. Además están las bibliotecas remotas, cuando se usan ejecutables separados para sistemas o arquitecturas distribuidas. Por último, se mencionan las bibliotecas de generación de código, que son una API de alto nivel utilizadas para generar o transformar el código de bytes en Java.

Linking y relocalización

También es importante conocer conceptos como:

  • Enlace (Linking): las bibliotecas son importantes en el proceso de enlace o vinculación de programas, que resuelve referencias conocidas como enlaces o símbolos a módulos de bibliotecas. El proceso de enlace generalmente se realiza automáticamente mediante un programa enlazador o vinculador que busca un conjunto de bibliotecas y otros módulos en un orden determinado. Por lo general, no se considera un error si un objetivo de enlace se encuentra múltiples veces en un conjunto dado de bibliotecas. El enlace puede realizarse al crear un archivo ejecutable (enlace estático) o cada vez que se utiliza el programa en tiempo de ejecución (enlace dinámico). Las referencias que se están resolviendo pueden ser direcciones para saltos y otras llamadas de rutina. Pueden estar en el programa principal o en un módulo que depende de otro. Se resuelven en direcciones fijas o reubicables (desde una base común) mediante la asignación de memoria en tiempo de ejecución para los segmentos de memoria de cada módulo referenciado. Algunos lenguajes de programación utilizan una característica llamada enlace inteligente, mediante la cual el enlazador es consciente o está integrado con el compilador, de modo que el enlazador sabe cómo se utilizan las referencias externas, y el código de una biblioteca que nunca se usa, aunque se haga referencia internamente, puede descartarse de la aplicación compilada. Por ejemplo, un programa que solo utiliza enteros para operaciones aritméticas, o que no realiza operaciones aritméticas en absoluto, puede excluir rutinas de biblioteca de punto flotante. Esta característica de enlace inteligente puede dar lugar a tamaños de archivo de aplicación más pequeños y un menor uso de memoria.
  • Reubicación (Relocation): algunas referencias en un programa o módulo de biblioteca se almacenan en una forma relativa o simbólica que no puede resolverse hasta que todo el código y las bibliotecas se asignen a direcciones estáticas finales. La reubicación es el proceso de ajustar estas referencias, y se realiza ya sea mediante el enlazador o el cargador. En general, no se puede hacer la reubicación a las bibliotecas individuales en sí mismas, porque las direcciones en memoria pueden variar según el programa que las utiliza y las otras bibliotecas con las que se combinan. El código independiente de la posición evita las referencias a direcciones absolutas y, por lo tanto, no requiere reubicación.

Jaime Herrera

Jaime Herrera

Ingeniero Informático apasionado por el hardware y la tecnología. Llevo más de diez años dedicándome al análisis de componentes como procesadores, tarjetas gráficas y sistemas de almacenamiento. Mi objetivo es ofrecer información clara y precisa, combinando mi experiencia técnica con un enfoque práctico para ayudar a los lectores a entender mejor el mundo del hardware.

>
Guía Hardware
Logo