¿Qué significa 'enlazado estáticamente' y 'enlazado dinámicamente'?

229

A menudo escucho los términos 'enlazados estáticamente' y 'enlazados dinámicamente', a menudo en referencia al código escrito en C , C ++ o C # . ¿Qué son, de qué están hablando exactamente y qué están vinculando?

UnkwnTech
fuente

Respuestas:

445

Hay (en la mayoría de los casos, descontando el código interpretado) dos etapas para pasar del código fuente (lo que escribe) al código ejecutable (lo que ejecuta).

El primero es la compilación que convierte el código fuente en módulos de objeto.

El segundo, la vinculación, es lo que combina módulos de objetos para formar un ejecutable.

La distinción se hace para, entre otras cosas, permitir que se incluyan bibliotecas de terceros en su ejecutable sin que vea su código fuente (como bibliotecas para acceso a bases de datos, comunicaciones de red e interfaces gráficas de usuario), o para compilar código en diferentes idiomas ( C y código de ensamblaje, por ejemplo) y luego vincularlos todos juntos.

Cuando vincula estáticamente un archivo a un archivo ejecutable, el contenido de ese archivo se incluye en el momento del enlace. En otras palabras, el contenido del archivo se inserta físicamente en el ejecutable que ejecutará.

Cuando se vincula dinámicamente , se incluye un puntero al archivo que se está vinculando (el nombre del archivo, por ejemplo) en el archivo ejecutable y el contenido de dicho archivo no se incluye en el momento del enlace. Es solo cuando más tarde ejecuta el ejecutable que estos archivos vinculados dinámicamente se compran y solo se compran en la copia en memoria del ejecutable, no en el disco.

Básicamente es un método de enlace diferido. Hay un método aún más diferido (llamado enlace tardío en algunos sistemas) que no traerá el archivo vinculado dinámicamente hasta que realmente intente llamar a una función dentro de él.

Los archivos enlazados estáticamente están 'bloqueados' al ejecutable en el momento del enlace para que nunca cambien. Un archivo vinculado dinámicamente al que hace referencia un ejecutable puede cambiar simplemente reemplazando el archivo en el disco.

Esto permite actualizaciones de la funcionalidad sin tener que volver a vincular el código; el cargador se vuelve a vincular cada vez que lo ejecuta.

Esto es bueno y malo: por un lado, permite actualizaciones más fáciles y correcciones de errores, por otro lado puede hacer que los programas dejen de funcionar si las actualizaciones son incompatibles; esto a veces es responsable del temido "infierno de DLL" que algunas personas mencione que las aplicaciones pueden romperse si reemplaza una biblioteca vinculada dinámicamente por una que no es compatible (por cierto, los desarrolladores que hacen esto deben esperar ser perseguidos y castigados severamente).


Como ejemplo , veamos el caso de un usuario que compila su main.carchivo para enlaces estáticos y dinámicos.

Phase     Static                    Dynamic
--------  ----------------------    ------------------------
          +---------+               +---------+
          | main.c  |               | main.c  |
          +---------+               +---------+
Compile........|.........................|...................
          +---------+ +---------+   +---------+ +--------+
          | main.o  | | crtlib  |   | main.o  | | crtimp |
          +---------+ +---------+   +---------+ +--------+
Link...........|..........|..............|...........|.......
               |          |              +-----------+
               |          |              |
          +---------+     |         +---------+ +--------+
          |  main   |-----+         |  main   | | crtdll |
          +---------+               +---------+ +--------+
Load/Run.......|.........................|..........|........
          +---------+               +---------+     |
          | main in |               | main in |-----+
          | memory  |               | memory  |
          +---------+               +---------+

Puede ver en el caso estático que el programa principal y la biblioteca de tiempo de ejecución C están vinculados entre sí en el momento del enlace (por los desarrolladores). Como el usuario generalmente no puede volver a vincular el ejecutable, está atascado con el comportamiento de la biblioteca.

En el caso dinámico, el programa principal está vinculado con la biblioteca de importación de tiempo de ejecución C (algo que declara lo que hay en la biblioteca dinámica pero en realidad no lo define ). Esto permite que el vinculador se vincule aunque falte el código real.

Luego, en tiempo de ejecución, el cargador del sistema operativo realiza un enlace tardío del programa principal con la DLL de tiempo de ejecución C (biblioteca de enlace dinámico o biblioteca compartida u otra nomenclatura).

El propietario del tiempo de ejecución de C puede colocar una nueva DLL en cualquier momento para proporcionar actualizaciones o correcciones de errores. Como se indicó anteriormente, esto tiene ventajas y desventajas.

paxdiablo
fuente
11
Corríjame si me equivoco, pero en Windows, el software tiende a incluir sus propias bibliotecas con la instalación, incluso si están vinculadas dinámicamente. En muchos sistemas Linux con un administrador de paquetes, muchas bibliotecas vinculadas dinámicamente ("objetos compartidos") en realidad se comparten entre el software.
Paul Fisher el
66
@PaulF: cosas como los controles comunes de Windows, DirectX, .NET, etc., se entregan mucho con las aplicaciones, mientras que en Linux, tiendes a usar apt o yum o algo así para administrar dependencias, así que tienes razón en ese sentido . Las aplicaciones de Win que envían su propio código como archivos DLL tienden a no compartirlas.
paxdiablo
31
Hay un lugar especial reservado en el noveno círculo del infierno para aquellos que actualizan sus DLL y rompen la compatibilidad con versiones anteriores. Sí, si las interfaces desaparecen o se modifican, la vinculación dinámica caerá en un montón. Por eso no debe hacerse. Por supuesto, agregue una función2 () a su DLL pero no cambie la función () si la gente lo está usando. La mejor manera de manejar eso es recodificar function () de tal manera que llame a function2 (), pero no cambie la firma de function ().
paxdiablo
1
@Paul Fisher, sé que es tarde, pero ... la biblioteca que se envía con una DLL de Windows no es la biblioteca completa, son solo un montón de trozos que le dicen al enlazador qué contiene la DLL. Luego, el vinculador puede poner automáticamente la información en el archivo .exe para cargar la DLL, y los símbolos no se muestran como indefinidos.
Mark Ransom
1
@Santropedro, tiene razón en todos los aspectos con respecto al significado de los nombres lib, import y DLL. El sufijo es solo una convención, así que no leas demasiado (por ejemplo, la DLL puede tener una .dllo una .soextensión); piensa en la respuesta como una explicación de los conceptos en lugar de ser una descripción exacta. Y, según el texto, este es un ejemplo que muestra enlaces estáticos y dinámicos solo para los archivos de tiempo de ejecución de C, así que sí, eso es lo que `crt indica en todos ellos.
paxdiablo
221

Creo que una buena respuesta a esta pregunta debería explicar qué es la vinculación .

Cuando compila un código C (por ejemplo), se traduce al lenguaje de máquina. Solo una secuencia de bytes que, cuando se ejecuta, hace que el procesador sume, reste, compare, "pase a", lea la memoria, escriba la memoria, ese tipo de cosas. Estas cosas se almacenan en archivos de objetos (.o).

Ahora, hace mucho tiempo, los informáticos inventaron esta cosa de "subrutina". Ejecute este trozo de código y regrese aquí. No pasó mucho tiempo antes de que se dieran cuenta de que las subrutinas más útiles podían almacenarse en un lugar especial y ser utilizadas por cualquier programa que las necesitara.

Ahora, en los primeros días, los programadores tendrían que introducir la dirección de memoria en la que se encontraban estas subrutinas. Algo así como CALL 0x5A62. Esto era tedioso y problemático en caso de que alguna vez fuera necesario cambiar esas direcciones de memoria.

Entonces, el proceso fue automatizado. Escribes un programa que llama printf()y el compilador no conoce la dirección de memoria de printf. Entonces, el compilador simplemente escribe CALL 0x0000y agrega una nota al archivo de objeto que dice "debe reemplazar este 0x0000 con la ubicación de memoria de printf ".

Enlace estático significa que el programa enlazador (el GNU se llama ld ) agrega printfel código de máquina directamente a su archivo ejecutable y cambia el 0x0000 a la dirección de printf. Esto sucede cuando se crea su ejecutable.

La vinculación dinámica significa que el paso anterior no sucede. El archivo ejecutable todavía tiene una nota que dice "debe reemplazar 0x000 con la ubicación de memoria de printf". El cargador del sistema operativo necesita encontrar el código printf, cargarlo en la memoria y corregir la dirección CALL cada vez que se ejecuta el programa .

Es común que los programas invoquen algunas funciones que estarán vinculadas estáticamente (las funciones de biblioteca estándar, como printflas que normalmente están vinculadas estáticamente) y otras funciones que están vinculadas dinámicamente. Los estáticos "se convierten en parte" del ejecutable y los dinámicos "se unen" cuando se ejecuta el ejecutable.

Hay ventajas y desventajas en ambos métodos, y hay diferencias entre los sistemas operativos. Pero como no preguntaste, terminaré esto aquí.

Artelius
fuente
44
Yo también, pero solo puedo elegir 1 respuesta.
UnkwnTech
1
Artelius, estoy buscando en profundidad tu explicación sobre cómo funcionan estas cosas locas de bajo nivel. responda con qué libros debemos leer para obtener un conocimiento profundo de las cosas anteriores. gracias.
mahesh
1
Lo siento, no puedo sugerir ningún libro. Deberías aprender el lenguaje ensamblador primero. Entonces Wikipedia puede dar una visión general decente de tales temas. Es posible que desee consultar la lddocumentación de GNU .
Artelius el
31

Las bibliotecas enlazadas estáticamente están enlazadas en tiempo de compilación. Las bibliotecas vinculadas dinámicamente se cargan en tiempo de ejecución. La vinculación estática hornea el bit de la biblioteca en su ejecutable. La vinculación dinámica solo se hornea en una referencia a la biblioteca; los bits para la biblioteca dinámica existen en otro lugar y podrían cambiarse más tarde.

John D. Cook
fuente
16

Debido a que ninguna de las publicaciones anteriores muestra realmente cómo vincular estáticamente algo y ver que lo hiciste correctamente, así que abordaré este problema:

Un simple programa en C

#include <stdio.h>

int main(void)
{
    printf("This is a string\n");
    return 0;
}

Enlace dinámicamente el programa C

gcc simpleprog.c -o simpleprog

Y correr fileen el binario:

file simpleprog 

Y eso mostrará que está vinculado dinámicamente a algo como:

"simpleprog: ELF ejecutable LSB de 64 bits, x86-64, versión 1 (SYSV), vinculado dinámicamente (usa libs compartidas), para GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, no eliminado"

En su lugar, vinculemos estáticamente el programa esta vez:

gcc simpleprog.c -static -o simpleprog

El archivo en ejecución en este binario enlazado estáticamente mostrará:

file simpleprog 

"simpleprog: ELF ejecutable LSB de 64 bits, x86-64, versión 1 (GNU / Linux), enlazado estáticamente, para GNU / Linux 2.6.26, BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b, no despojado"

Y puedes ver que está felizmente vinculado estáticamente. Lamentablemente, sin embargo, no todas las bibliotecas son fáciles de vincular estáticamente de esta manera y pueden requerir un esfuerzo extendido usando libtoolo vinculando el código objeto y las bibliotecas C a mano.

Afortunadamente, muchas bibliotecas C incrustadas muslofrecen opciones de enlace estático para casi todas, si no todas, sus bibliotecas.

Ahora straceel binario que ha creado y puede ver que no hay bibliotecas accedidas antes de que comience el programa:

strace ./simpleprog

¡Ahora compare con la salida del straceprograma vinculado dinámicamente y verá que la versión de la versión estáticamente vinculada es mucho más corta!


fuente
2

(No sé C # pero es interesante tener un concepto de enlace estático para un lenguaje VM)

La vinculación dinámica implica saber cómo encontrar una funcionalidad requerida que solo tiene una referencia de su programa. Su tiempo de ejecución de lenguaje o sistema operativo busca un fragmento de código en el sistema de archivos, red o caché de código compilado, que coincida con la referencia, y luego toma varias medidas para integrarlo a la imagen de su programa en la memoria, como la reubicación. Todos se hacen en tiempo de ejecución. Se puede hacer manualmente o por el compilador. Existe la posibilidad de actualizar con el riesgo de equivocarse (es decir, DLL hell).

La vinculación estática se realiza en el momento de la compilación, le dice al compilador dónde están todas las partes funcionales y le indica que las integre. No hay búsqueda, no hay ambigüedad, no se puede actualizar sin una nueva compilación. Todas sus dependencias son físicamente una con la imagen de su programa.

artificialidiot
fuente