O que significa “vinculado estaticamente” e “vinculado dinamicamente”?

Muitas vezes ouço os termos “vinculados estaticamente” e “vinculados dinamicamente”, muitas vezes com referência ao código escrito em C , C ++ ou C # , mas não sei muito sobre nada. O que eles estão, o que exatamente eles estão falando e o que estão ligando?

Há (na maioria dos casos, descontando o código interpretado) dois estágios na obtenção do código-fonte (o que você escreve) para o código executável (o que você executa).

A primeira é a compilation que transforma o código fonte em módulos de objects.

A segunda, ligando, é o que combina os módulos de objects para formar um executável.

A distinção é feita para, entre outras coisas, permitir que bibliotecas de terceiros sejam incluídas em seu executável sem você ver seu código-fonte (como bibliotecas para access a database, comunicações de rede e interfaces gráficas) ou para compilar código em diferentes idiomas ( C e código de assembly, por exemplo) e, em seguida, ligando-os todos juntos.

Quando você vincula estaticamente um arquivo a um executável, o conteúdo desse arquivo é incluído no momento do link. Em outras palavras, o conteúdo do arquivo é fisicamente inserido no executável que você executará.

Quando você vincula dinamicamente , um ponteiro para o arquivo que está sendo vinculado (o nome do arquivo do arquivo, por exemplo) é incluído no executável e o conteúdo do arquivo não é incluído no momento do link. É somente quando você executa o executável que esses arquivos vinculados dinamicamente são comprados e eles são comprados somente na cópia em memory do executável, não no disco.

É basicamente um método de vinculação adiada. Há um método ainda mais diferido (chamado de binding tardia em alguns sistemas) que não trará o arquivo vinculado dinamicamente até que você realmente tente chamar uma function dentro dele.

Os arquivos vinculados estaticamente são “bloqueados” para o executável no momento do link, para que nunca sejam alterados. Um arquivo dinamicamente vinculado referenciado por um executável pode ser alterado apenas substituindo o arquivo no disco.

Isso permite atualizações na funcionalidade sem precisar vincular novamente o código; o carregador re-liga toda vez que você o executa.

Isso é bom e ruim – por um lado, permite atualizações mais fáceis e correções de bugs, por outro pode levar programas a deixar de funcionar se as atualizações forem incompatíveis – às vezes é responsável pelo temido “inferno da DLL” que algumas pessoas mencionar que os aplicativos podem ser quebrados se você replace uma biblioteca vinculada dinamicamente por uma que não seja compatível (desenvolvedores que fazem isso devem ser caçados e punidos severamente, a propósito).


Como exemplo , vamos ver o caso de um usuário que compila seu arquivo main.c para vinculação estática e 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 | +---------+ +---------+ 

Você pode ver no caso estático que o programa principal e a biblioteca de tempo de execução C estão vinculados juntos no momento do link (pelos desenvolvedores). Como o usuário normalmente não pode vincular novamente o executável, ele fica preso ao comportamento da biblioteca.

No caso dynamic, o programa principal é vinculado à biblioteca de importação de tempo de execução C (algo que declara o que está na biblioteca dinâmica, mas não o define de fato). Isso permite que o vinculador vincule mesmo que o código real esteja ausente.

Em seguida, em tempo de execução, o carregador do sistema operacional faz uma binding tardia do programa principal com a DLL de tempo de execução C (biblioteca de vínculo dynamic ou biblioteca compartilhada ou outra nomenclatura).

O proprietário do tempo de execução C pode include uma nova DLL a qualquer momento para fornecer atualizações ou correções de erros. Como afirmado anteriormente, isso tem vantagens e desvantagens.

Eu acho que uma boa resposta a essa pergunta deveria explicar o que é a binding.

Quando você compila algum código C (por exemplo), ele é traduzido para o idioma da máquina. Apenas uma sequência de bytes que, quando executada, faz com que o processador adicione, subtraia, compare, “goto”, leia memory, escreva memory, esse tipo de coisa. Este material é armazenado em arquivos object (.o).

Agora, há muito tempo, cientistas da computação inventaram essa coisa de “sub-rotina”. Execute-este-pedaço-de-código-e-devolve-aqui. Não demorou muito para que percebessem que as sub-rotinas mais úteis podiam ser armazenadas em um lugar especial e usadas por qualquer programa que precisasse delas.

Agora, nos primeiros dias, os programadores precisavam digitar o endereço de memory em que essas sub-rotinas estavam localizadas. Algo como CALL 0x5A62 . Isso era tedioso e problemático se esses endereços de memory precisassem ser alterados.

Então, o processo foi automatizado. Você escreve um programa que chama printf() e o compilador não conhece o endereço de memory do printf . Portanto, o compilador apenas grava CALL 0x0000 e adiciona uma observação ao arquivo de object dizendo “deve replace este 0x0000 com o local de memory do printf “.

Vínculo estático significa que o programa de link (o GNU é chamado ld ) adiciona o código de máquina do printf diretamente ao seu arquivo executável e altera o 0x0000 para o endereço do printf . Isso acontece quando o seu executável é criado.

Ligação dinâmica significa que o passo acima não acontece. O arquivo executável ainda tem uma anotação que diz “deve replace 0x000 com o local da memory do printf”. O carregador do sistema operacional precisa encontrar o código printf, carregá-lo na memory e corrigir o endereço CALL, toda vez que o programa for executado .

É comum que os programas chamem algumas funções que serão vinculadas estaticamente (funções de biblioteca padrão como printf são geralmente vinculadas estaticamente) e outras funções que são dinamicamente vinculadas. Os estáticos “tornam-se parte” do executável e os dynamics “participam” quando o executável é executado.

Existem vantagens e desvantagens para ambos os methods, e existem diferenças entre os sistemas operacionais. Mas desde que você não perguntou, eu terminarei isto aqui.

Bibliotecas vinculadas estaticamente são vinculadas em tempo de compilation. Bibliotecas vinculadas dinamicamente são carregadas em tempo de execução. A vinculação estática assola o bit da biblioteca em seu executável. Vinculação dinâmica apenas faz uma referência à biblioteca; os bits para a biblioteca dinâmica existem em outro lugar e podem ser trocados posteriormente.

Porque nenhuma das postagens acima realmente mostra como vincular estaticamente algo e ver que você o fez corretamente, então vou abordar esse problema:

Um programa simples em C

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

Vincular dinamicamente o programa C

 gcc simpleprog.c -o simpleprog 

E execute o file no binário:

 file simpleprog 

E isso vai mostrar que está dinamicamente ligado a algo como:

“simpleprog: executável LSB de 64 bits ELF, x86-64, versão 1 (SYSV), dinamicamente vinculado (usa libs compartilhadas), para GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, não retirado”

Em vez disso, vamos associar estaticamente o programa desta vez:

 gcc simpleprog.c -static -o simpleprog 

A execução do arquivo nesse binário vinculado estaticamente mostrará:

 file simpleprog 

“simpleprog: executável LSB de 64 bits ELF, x86-64, versão 1 (GNU / Linux), vinculado estaticamente, para GNU / Linux 2.6.26, BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b, não retirado”

E você pode ver que está felizmente ligado estaticamente. Infelizmente, no entanto, nem todas as bibliotecas são simples de vincular estaticamente dessa forma e podem exigir um esforço estendido usando o libtool ou vinculando o código do object e as bibliotecas C manualmente.

Felizmente, muitas bibliotecas C incorporadas, como a musl oferecem opções de vinculação estática para quase todas, se não todas as suas bibliotecas.

Agora strace o binário que você criou e você pode ver que não há bibliotecas acessadas antes do início do programa:

 strace ./simpleprog 

Agora compare com a saída de strace no programa vinculado dinamicamente e você verá que o strace da versão estaticamente vinculada é muito mais curto!

(Eu não sei c # mas é interessante ter um conceito de link estático para uma linguagem VM)

Vinculação dinâmica envolve saber como encontrar uma funcionalidade necessária que você só tem uma referência do seu programa. Você language tempo de execução ou OS busca por um pedaço de código no sistema de arquivos, rede ou cache de código compilado, combinando a referência e, em seguida, leva várias medidas para integrá-lo à sua imagem de programa na memory, como realocação. Eles são todos feitos em tempo de execução. Isso pode ser feito manualmente ou pelo compilador. Há capacidade de atualizar com o risco de estragar (ou seja, o inferno DLL).

Ligação estática é feita em tempo de compilation que, você diz ao compilador onde todas as partes funcionais estão e instrui para integrá-las. Não há busca, nem ambigüidade, nem capacidade de atualização sem uma recompilation. Todas as suas dependencies são fisicamente uma com sua imagem de programa.