O que significam 'estaticamente vinculado' e 'dinamicamente vinculado'?

229

Costumo ouvir os termos 'estaticamente vinculados' e 'dinamicamente vinculados', frequentemente em referência ao código escrito em C , C ++ ou C # . O que eles estão falando, o que exatamente eles estão falando e o que eles estão vinculando?

UnkwnTech
fonte

Respostas:

445

Existem (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 compilação que transforma o código fonte em módulos de objetos.

O segundo, vinculação, é o que combina módulos de objetos para formar um executável.

A distinção é feita para, entre outras coisas, permitir que bibliotecas de terceiros sejam incluídas no seu executável sem que você veja o código-fonte (como bibliotecas para acesso ao banco de dados, comunicações de rede e interfaces gráficas de usuário) ou para compilar código em diferentes idiomas ( C e código de montagem, por exemplo) e, em seguida, vinculá-los 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, por exemplo) é incluído no executável e o conteúdo do referido arquivo não é incluído no momento do link. Somente depois que você executa o executável é que esses arquivos vinculados dinamicamente são comprados e comprados apenas na cópia do executável na memória, não no disco.

É basicamente um método de vinculação diferida. Existe um método ainda mais diferido (chamado ligação tardia em alguns sistemas) que não trará o arquivo vinculado dinamicamente até que você tente chamar uma função nele.

Arquivos vinculados estaticamente são 'bloqueados' no executável no momento do link, para que nunca sejam alterados. Um arquivo vinculado dinamicamente referenciado por um executável pode mudar apenas substituindo o arquivo no disco.

Isso permite atualizações na funcionalidade sem precisar vincular novamente o código; o carregador é vinculado novamente 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 a programas que deixam de funcionar se as atualizações forem incompatíveis - às vezes isso é responsável pelo temido "inferno da DLL" que algumas pessoas mencione que os aplicativos podem ser quebrados se você substituir uma biblioteca vinculada dinamicamente por uma que não seja compatível (os desenvolvedores que fazem isso devem esperar ser caçados e punidos severamente, a propósito).


Como exemplo , vejamos o caso de um usuário compilando seu main.carquivo 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 dinâmico, o programa principal está vinculado à biblioteca de importação de tempo de execução C (algo que declara o que há na biblioteca dinâmica, mas na verdade não a define ). Isso permite que o vinculador vincule, mesmo que o código real esteja ausente.

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

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

paxdiablo
fonte
11
Corrija-me se estiver errado, mas no Windows, o software tende a incluir suas próprias bibliotecas com a instalação, mesmo que estejam dinamicamente vinculadas. Em muitos sistemas Linux com um gerenciador de pacotes, muitas bibliotecas vinculadas dinamicamente ("objetos compartilhados") são realmente compartilhadas entre softwares.
Paul Fisher
6
@PaulF: coisas como os controles comuns do Windows, DirectX, .NET etc. são muito comuns com os aplicativos, enquanto no Linux você tende a usar o apt ou o yum ou algo assim para gerenciar dependências - então você está certo nesse sentido . Os aplicativos Win que fornecem seu próprio código como DLLs tendem a não compartilhá-los.
paxdiablo
31
Há um lugar especial reservado no nono círculo do inferno para aqueles que atualizam suas DLLs e quebram a compatibilidade com versões anteriores. Sim, se as interfaces desaparecerem ou forem modificadas, o vínculo dinâmico cairá em um monte. É por isso que não deve ser feito. Adicione, por todos os meios, uma function2 () à sua DLL, mas não altere a função () se as pessoas estiverem usando. A melhor maneira de lidar com isso é recodificar a função () da maneira que chama function2 (), mas não altere a assinatura da função ().
22410
1
@ Paul Fisher, eu sei que isso é tarde, mas ... a biblioteca que acompanha uma DLL do Windows não é a biblioteca completa, é apenas um monte de stubs que informam ao vinculador o que a DLL contém. O vinculador pode colocar automaticamente as informações no .exe para carregar a DLL e os símbolos não aparecem como indefinidos.
Mark Ransom
1
@ Santropedro, você está correto em todos os aspectos quanto ao significado dos nomes da lib, da importação e da DLL. O sufixo é apenas uma convenção, portanto, não leia muito sobre isso (por exemplo, a DLL pode ter uma .dllou .soextensão) - pense na resposta como explicando os conceitos, em vez de ser uma descrição exata. E, de acordo com o texto, este é um exemplo que mostra links estáticos e dinâmicos apenas para os arquivos de tempo de execução C, portanto, sim, é isso que `crt indica em todos eles.
paxdiablo
221

Eu acho que uma boa resposta para essa pergunta deve explicar o que é vinculação .

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, "vá", leia memória, escreva memória, esse tipo de coisa. Esse material é armazenado em arquivos de objeto (.o).

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

Agora, nos primeiros dias, os programadores precisariam digitar o endereço de memória em que essas sub-rotinas estavam localizadas. Algo como CALL 0x5A62. Isso foi entediante e problemático, caso esses endereços de memória precisem ser alterados.

Então, o processo foi automatizado. Você escreve um programa que chama printf()e o compilador não sabe o endereço de memória de printf. Portanto, o compilador apenas grava CALL 0x0000e adiciona uma nota ao arquivo de objeto dizendo "deve substituir este 0x0000 pelo local da memória de printf ".

Ligação estática significa que o programa vinculador (o GNU é chamado ld ) adiciona printfo código de máquina diretamente ao seu arquivo executável e altera o 0x0000 para o endereço de printf. Isso acontece quando seu executável é criado.

A ligação dinâmica significa que a etapa acima não acontece. O arquivo executável ainda possui uma observação que diz "deve substituir 0x000 pelo local da memória de printf". O carregador do sistema operacional precisa encontrar o código printf, carregá-lo na memória e corrigir o endereço CALL, sempre que o programa for executado .

É comum os programas chamarem algumas funções que serão vinculadas estaticamente (funções de biblioteca padrão como printfgeralmente são vinculadas estaticamente) e outras funções que são vinculadas dinamicamente. Os estáticos "se tornam parte" do executável e os dinâmicos "se juntam" quando o executável é executado.

Existem vantagens e desvantagens nos dois métodos e diferenças entre os sistemas operacionais. Mas como você não pediu, eu terminarei isso aqui.

Artelius
fonte
4
Eu também fiz, no entanto, só posso escolher 1 resposta.
UnkwnTech 23/11
1
Artelius, estou analisando detalhadamente sua explicação sobre como essas coisas loucas de baixo nível funcionam. responda com quais livros devemos ler para obter conhecimento aprofundado sobre as coisas acima. obrigado.
Mahesh
1
Desculpe, não posso sugerir livros. Você deve aprender a linguagem assembly primeiro. Então a Wikipedia pode fornecer uma visão geral decente desses tópicos. Você pode querer olhar para a lddocumentação do GNU .
Artelius 14/04/2009
31

Bibliotecas vinculadas estaticamente são vinculadas no momento da compilação. Bibliotecas vinculadas dinamicamente são carregadas em tempo de execução. A vinculação estática transforma o bit da biblioteca em seu executável. A vinculação dinâmica é apenas uma referência à biblioteca; os bits da biblioteca dinâmica existem em outros lugares e podem ser trocados mais tarde.

John D. Cook
fonte
16

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

Um programa C simples

#include <stdio.h>

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

Vincular dinamicamente o programa C

gcc simpleprog.c -o simpleprog

E execute fileno binário:

file simpleprog 

E isso mostrará que está vinculado dinamicamente algo ao longo das linhas de:

"simpleprog: executável LSF ELF de 64 bits, x86-64, versão 1 (SYSV), vinculado dinamicamente (usa libs compartilhadas), para GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, sem remoção"

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

gcc simpleprog.c -static -o simpleprog

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

file simpleprog 

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

E você pode ver que ele está vinculado estaticamente. Infelizmente, porém, nem todas as bibliotecas são simples de vincular estaticamente dessa maneira e podem exigir um esforço prolongado usando libtoolou vinculando o código de objeto e as bibliotecas C manualmente.

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

Agora, straceo 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 do straceprograma vinculado dinamicamente e você verá que o rastreio da versão vinculada estaticamente é muito menor!


fonte
2

(Não sei c #, mas é interessante ter um conceito de vinculação estática para uma linguagem de VM)

A vinculação dinâmica envolve saber como encontrar uma funcionalidade necessária, que você só tem uma referência do seu programa. O tempo de execução de idioma ou o SO procura um trecho de código no sistema de arquivos, na rede ou no cache de código compilado, correspondendo à referência e, em seguida, toma várias medidas para integrá-lo à imagem do programa na memória, como realocação. Todos eles são feitos em tempo de execução. Isso pode ser feito manualmente ou pelo compilador. Existe a capacidade de atualizar com o risco de estragar (a saber, DLL inferno).

A vinculação estática é feita no momento da compilação; você informa ao compilador onde estão todas as partes funcionais e instrui-o a integrá-las. Não há pesquisa, ambiguidade, capacidade de atualização sem recompilação. Todas as suas dependências são fisicamente uma com a imagem do programa.

artificialidiot
fonte