Pregunta

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?

¿Fue útil?

Solución

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 lenguajes (C y código de ensamblaje, por ejemplo) y luego vincularlos todos juntos.

Cuando estáticamente vincula un archivo a un 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 vincula dinámicamente , se incluye un puntero al archivo que se está vinculando (el nombre del archivo, por ejemplo) en el ejecutable y el contenido de dicho archivo no se incluye en el enlace hora. 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.

Es básicamente un método de enlace diferido. Incluso hay un método diferido más (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 mencionan en que las aplicaciones pueden romperse si reemplaza una biblioteca vinculada dinámicamente por una que no es compatible (los desarrolladores que hacen esto deben esperar ser perseguidos y castigados severamente, por cierto).


Como ejemplo , veamos el caso de un usuario que compila su archivo main.c para vinculación estática y dinámica.

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 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.

Otros consejos

Creo que una buena respuesta a esta pregunta debería explicar qué es vincular .

Cuando compila algún 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 "subrutina" cosa. Ejecute este fragmento 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 si esas direcciones de memoria alguna vez debían cambiarse.

Entonces, el proceso fue automatizado. Usted escribe un programa que llama a printf () y el compilador no conoce la dirección de memoria de printf . Por lo tanto, el compilador simplemente escribe CALL 0x0000 y 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 printf directamente a su archivo ejecutable, y cambia el 0x0000 a la dirección de printf . Esto sucede cuando se crea su ejecutable.

Enlace dinámico significa que el paso anterior no sucede. El archivo ejecutable still 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 printf generalmente 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 en '' cuando se ejecuta el ejecutable.

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

Las bibliotecas vinculadas estáticamente se vinculan 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.

Debido a que ninguna de las publicaciones anteriores realmente muestra 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;
}

Vincula dinámicamente el programa C

gcc simpleprog.c -o simpleprog

Y ejecute el archivo en 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, permitamos vincular estáticamente el programa esta vez:

gcc simpleprog.c -static -o simpleprog

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

strace ./simpleprog

" simpleprog: ELF 64-bit LSB ejecutable, 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. Sin embargo, lamentablemente no todas las bibliotecas son fáciles de vincular estáticamente de esta manera y pueden requerir un esfuerzo extenso usando libtool o vinculando el código objeto y las bibliotecas C a mano.

Afortunadamente, muchas bibliotecas C incrustadas como musl ofrecen opciones de enlace estático para casi todas las si no todas de sus bibliotecas.

Ahora strace el binario que ha creado y puede ver que no se accede a bibliotecas antes de que comience el programa:

<*>

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

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

El enlace dinámico implica saber cómo encontrar una funcionalidad requerida a la que solo tiene una referencia de su programa. El tiempo de ejecución del idioma o el sistema operativo buscan 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 desordenar (es decir, DLL infierno).

La vinculación estática se realiza en el momento de la compilación, le indica 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top