Por que a ordem na qual as bibliotecas são vinculadas às vezes causa erros no GCC?


Respostas:


558

(Veja o histórico desta resposta para obter um texto mais elaborado, mas agora acho que é mais fácil para o leitor ver linhas de comando reais).


Arquivos comuns compartilhados por todos os comandos abaixo

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

Vinculando a Bibliotecas Estáticas

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

O vinculador pesquisa da esquerda para a direita e observa os símbolos não resolvidos. Se uma biblioteca resolver o símbolo, serão necessários os arquivos de objeto dessa biblioteca para resolvê-lo (neste caso, sai da libb.a).

As dependências das bibliotecas estáticas funcionam uma da outra - a biblioteca que precisa de símbolos deve ser a primeira e, em seguida, a biblioteca que resolve o símbolo.

Se uma biblioteca estática depende de outra biblioteca, mas a outra biblioteca depende novamente da biblioteca anterior, há um ciclo. Você pode resolver isso encerrando as bibliotecas dependentes de ciclismo por -(e -), como -( -la -lb -)(você pode precisar escapar das parênteses, como -\(e -\)). O vinculador, em seguida, procura na biblioteca incluída várias vezes para garantir que as dependências de ciclismo sejam resolvidas. Alternativamente, você pode especificar as bibliotecas várias vezes, por isso, cada um é antes um outro: -la -lb -la.

Vinculando a Bibliotecas Dinâmicas

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

É o mesmo aqui - as bibliotecas devem seguir os arquivos de objeto do programa. A diferença aqui em comparação com as bibliotecas estáticas é que você não precisa se preocupar com as dependências das bibliotecas umas contra as outras, porque as bibliotecas dinâmicas ordenam suas próprias dependências .

Aparentemente, algumas distribuições recentes usam o --as-neededsinalizador vinculador, o que força os arquivos de objeto do programa antes das bibliotecas dinâmicas. Se esse sinalizador for passado, o vinculador não fará o link para bibliotecas que não são realmente necessárias para o executável (e o detecta da esquerda para a direita). Minha distribuição recente do archlinux não usa esse sinalizador por padrão, portanto não deu um erro por não seguir a ordem correta.

Não é correto omitir a dependência de b.socontra d.soao criar o primeiro. Você será solicitado a especificar a biblioteca ao vincular a, mas na averdade não precisa do número inteiro b, portanto não deve se preocupar com bas próprias dependências.

Aqui está um exemplo das implicações se você deixar de especificar as dependências para libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

Se você agora examinar quais dependências o binário possui, observe que o próprio binário depende também libd, não apenas libbcomo deveria. O binário precisará ser vinculado novamente se libbposteriormente depender de outra biblioteca, se você fizer dessa maneira. E se outra pessoa carregar libbusando dlopenem tempo de execução (pense em carregar plugins dinamicamente), a chamada também falhará. Então o "right"realmente deveria ser um wrongtambém.


10
Repita até que todos os símbolos sejam resolvidos, eh - você acha que eles podem gerenciar uma classificação topológica. O LLVM possui 78 bibliotecas estáticas por conta própria, com dependências de quem sabe o quê. É verdade que ele também possui um script para descobrir as opções de compilação / link - mas você não pode usá-lo em todas as circunstâncias.
Steve314

6
@ Steve é ​​o que os programas lorder+ tsortfazem. Mas, às vezes, não há ordem, se você tiver referências cíclicas. Depois, basta percorrer a lista de bibliotecas até que tudo esteja resolvido.
Johannes Schaub - litb 18/08/19

10
@Johannes - Determine os componentes máximos fortemente conectados (por exemplo, o algoritmo Tarjans) e classifique topologicamente o dígrafo (inerentemente não cíclico) dos componentes. Cada componente pode ser tratado como uma biblioteca - se qualquer biblioteca do componente for necessária, o (s) ciclo (s) de dependência fará com que todas as bibliotecas desse componente sejam necessárias. Portanto, não, realmente não há necessidade de percorrer todas as bibliotecas para resolver tudo, e não há opções difíceis de linha de comando - um método que usa dois algoritmos conhecidos pode lidar com todos os casos corretamente.
Steve314

4
Gostaria de acrescentar um detalhe importante a esta excelente resposta: Usar "- (archives -)" ou "--start-group archives --end-group" é a única maneira segura de resolver dependências circulares , pois cada vez o vinculador visita um arquivo, extrai (e registra os símbolos não resolvidos) apenas os arquivos de objeto que resolvem os símbolos não resolvidos no momento . Por esse motivo, o algoritmo do CMake de repetir componentes conectados no gráfico de dependência pode ocasionalmente falhar. (Veja também excelente post de Ian Lance Taylor em ligadores para mais detalhes.)
Jørgen

3
Sua resposta me ajudou a resolver meus erros de vinculação e você explicou muito claramente como evitar problemas, mas você tem alguma idéia de por que foi projetado para funcionar dessa maneira?
precisa

102

O vinculador GNU ld é o chamado vinculador inteligente. Ele acompanhará as funções usadas pelas bibliotecas estáticas anteriores, descartando permanentemente as funções que não são usadas em suas tabelas de pesquisa. O resultado é que, se você vincular uma biblioteca estática muito cedo, as funções nessa biblioteca não estarão mais disponíveis para bibliotecas estáticas posteriormente na linha de link.

O vinculador típico do UNIX funciona da esquerda para a direita; portanto, coloque todas as suas bibliotecas dependentes à esquerda e aquelas que satisfazem essas dependências à direita da linha de link. Você pode achar que algumas bibliotecas dependem de outras, enquanto outras bibliotecas dependem delas. É aqui que fica complicado. Quando se trata de referências circulares, corrija seu código!


2
Isso é algo apenas com gnu ld / gcc? Ou isso é algo comum com vinculadores?
Mike

2
Aparentemente, mais compiladores Unix têm problemas semelhantes. O MSVC não está totalmente livre desses problemas, mas não parece tão ruim assim.
MSalters

4
As ferramentas de desenvolvimento MS não tendem a mostrar esses problemas tanto porque, se você usa uma cadeia de ferramentas totalmente MS, acaba configurando o pedido do vinculador corretamente e nunca percebe o problema.
Michael Kohne

16
O vinculador MSVC é menos sensível a esse problema, pois procurará todas as bibliotecas por um símbolo não referenciado. A ordem da biblioteca ainda pode afetar qual símbolo será resolvido se mais de uma biblioteca tiver o símbolo. No MSDN: "As bibliotecas também são pesquisadas na ordem da linha de comando, com a seguinte ressalva: Os símbolos não resolvidos ao trazer um arquivo de objeto de uma biblioteca são pesquisados ​​nessa biblioteca primeiro e, em seguida, nas seguintes bibliotecas na linha de comando e / DEFAULTLIB (especificar biblioteca padrão) e, em seguida, a todas as bibliotecas no início da linha de comando "
Michael Burr

4
"... vinculador inteligente ..." - acredito que seja classificado como vinculador de "passagem única", não como "vinculador inteligente".
JWW

54

Aqui está um exemplo para deixar claro como as coisas funcionam com o GCC quando bibliotecas estáticas estão envolvidas. Então, vamos supor que temos o seguinte cenário:

  • myprog.o- contendo main()função, dependente delibmysqlclient
  • libmysqlclient- estático, pelo bem do exemplo (você prefere a biblioteca compartilhada, é claro, porque libmysqlclienté enorme); in /usr/local/lib; e dependente de coisas delibz
  • libz (dinâmico)

Como vinculamos isso? (Nota: exemplos de compilação no Cygwin usando o gcc 4.3.4)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works

31

Se você adicionar -Wl,--start-groupaos sinalizadores do vinculador, não importa em que ordem eles estão ou se existem dependências circulares.

No Qt, isso significa adicionar:

QMAKE_LFLAGS += -Wl,--start-group

Economiza muito tempo brincando e não parece diminuir muito a velocidade de vinculação (o que leva muito menos tempo do que a compilação).


8

Outra alternativa seria especificar a lista de bibliotecas duas vezes:

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

Fazendo isso, você não precisa se preocupar com a sequência correta, pois a referência será resolvida no segundo bloco.


5

Você pode usar a opção -Xlinker.

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

é quase igual a

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

Cuidado !

  1. A ordem dentro de um grupo é importante! Aqui está um exemplo: uma biblioteca de depuração tem uma rotina de depuração, mas a biblioteca não de depuração tem uma versão fraca da mesma. Você deve colocar a biblioteca de depuração PRIMEIRO no grupo ou resolverá a versão sem depuração.
  2. Você precisa preceder cada biblioteca na lista de grupos com -Xlinker

5

Uma dica rápida que me surpreendeu: se você estiver chamando o vinculador como "gcc" ou "g ++", usar "--start-group" e "--end-group" não passará essas opções para o vinculador - nem sinalizará um erro. Ele apenas falhará no link com símbolos indefinidos, se a ordem da biblioteca estiver errada.

Você precisa escrevê-los como "-Wl, - start-group" etc. para informar ao GCC para passar o argumento para o vinculador.


2

A ordem dos links certamente importa, pelo menos em algumas plataformas. Vi falhas em aplicativos vinculados a bibliotecas na ordem errada (onde errado significa A vinculado antes de B, mas B depende de A).


2

Eu já vi isso muito, alguns de nossos módulos vinculam mais de 100 bibliotecas do nosso código, mais sistema e bibliotecas de terceiros.

Dependendo dos diferentes vinculadores HP / Intel / GCC / SUN / SGI / IBM / etc, você pode obter funções / variáveis ​​não resolvidas etc., em algumas plataformas é necessário listar as bibliotecas duas vezes.

Na maioria das vezes, usamos hierarquia estruturada de bibliotecas, núcleo, plataforma, diferentes camadas de abstração, mas para alguns sistemas você ainda precisa jogar com a ordem no comando link.

Depois de encontrar um documento de solução, o próximo desenvolvedor não precisará trabalhar novamente.

Meu antigo professor costumava dizer: " alta coesão e baixo acoplamento ", ainda é verdade hoje.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.