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

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

   

(Veja o histórico desta resposta para obter o texto mais elaborado, mas agora acho mais fácil para o leitor ver as 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 bo $ ar cr libb.a bo $ g++ -c d.cpp -o do $ ar cr libd.a do $ 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, os arquivos de objects dessa biblioteca serão removidos para resolver o símbolo (bo de libb.a neste caso).

Dependências de bibliotecas estáticas umas contra as outras funcionam da mesma forma – a biblioteca que precisa de símbolos deve ser a primeira, depois a biblioteca que resolve o símbolo.

Se uma biblioteca estática depender de outra biblioteca, mas a outra biblioteca depender novamente da antiga biblioteca, haverá um ciclo. Você pode resolver isso colocando as bibliotecas ciclicamente dependentes por -( e -) , como -( -la -lb -) (talvez seja necessário escaping dos parênsens, como -\( e -\) ). Em seguida, o vinculador pesquisa essas bibliotecas incluídas várias vezes para garantir que as dependencies de ciclos sejam resolvidas. Como alternativa, você pode especificar as bibliotecas várias vezes, portanto, cada uma é antes uma da outra: -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 object do programa. A diferença aqui com as bibliotecas estáticas é que você não deve se preocupar com as dependencies das bibliotecas umas contra as outras, porque as bibliotecas dinâmicas resolvem suas próprias dependencies .

Algumas distribuições recentes aparentemente --as-needed o padrão de usar o sinalizador de vinculador --as-needed , que impõe que os arquivos de object do programa cheguem antes das bibliotecas dinâmicas. Se esse sinalizador for passado, o vinculador não vinculará a bibliotecas que não são realmente necessárias pelo executável (e detecta isso da esquerda para a direita). Minha distribuição recente do archlinux não usa esse sinalizador por padrão, por isso não ocorreu um erro por não seguir a ordem correta.

Não é correto omitir a dependência de b.so contra d.so ao criar o primeiro. Você será solicitado a especificar a biblioteca ao vincular a then, mas realmente não precisa do inteiro b , portanto, ele não deve se preocupar com as próprias dependencies do b .

Aqui está um exemplo das implicações se você perder a especificação das dependencies 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ê olhar agora para quais dependencies o binário possui, você libd que o próprio binário depende também da libd , não apenas da libb como deveria. O binário precisará ser libb se libb posteriormente depender de outra biblioteca, se você fizer dessa maneira. E se alguém carregar libb usando o dlopen em tempo de execução (pense em carregar plugins dinamicamente), a chamada falhará também. Então, o "right" deveria ser wrong também.

O link de GNU ld é um chamado linkador inteligente. Ele acompanhará as funções usadas pelas bibliotecas estáticas precedentes, eliminando permanentemente as funções que não são usadas em suas tabelas de consulta. 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 as 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 dependencies à direita da linha de link. Você pode descobrir que algumas bibliotecas dependem de outras enquanto, ao mesmo tempo, outras bibliotecas dependem delas. É aí que fica complicado. Quando se trata de referências circulares, corrija seu código!

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 a function main() , dependente do libmysqlclient
  • libmysqlclient – static, para o exemplo (você prefere a biblioteca compartilhada, é claro, já que o libmysqlclient é enorme); em /usr/local/lib ; e dependente de coisas da libz
  • libz (dynamic)

Como ligamos isso? (Nota: exemplos de compilation no Cygwin usando 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 

Se você adicionar -Wl,--start-group aos sinalizadores do vinculador, não se importa em qual ordem eles estão ou se há dependencies circulares.

No Qt isso significa adicionar:

 QMAKE_LFLAGS += -Wl,--start-group 

Economiza muito tempo mexendo e não parece diminuir muito a binding (o que leva muito menos tempo que a compilation).

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.

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 debugging tem uma rotina de debugging, mas a biblioteca não-debugging tem uma versão fraca do mesmo. Você deve colocar a biblioteca de debugging em PRIMEIRO no grupo ou você irá resolver para a versão não-debugging.
  2. Você precisa preceder cada biblioteca na lista de grupos com -Xlinker

Uma dica rápida que me enganou: se você está invocando o linker como “gcc” ou “g ++”, usar “-start-group” e “–end-group” não passará essas opções para o linker – nem irá sinalizar um erro. Ele apenas falhará no link com símbolos indefinidos se você tiver a ordem da biblioteca errada.

Você precisa escrevê-los como “-Wl, – start-group” etc. para dizer ao GCC para passar o argumento para o linker.

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

Dependendo de diferentes linkers HP / Intel / GCC / SUN / SGI / IBM / etc você pode obter funções não resolvidas / variables ​​etc, em algumas plataformas você tem que listar as bibliotecas duas vezes.

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

Assim que você encontrar um documento de solução, o próximo desenvolvedor não terá que resolvê-lo novamente.

Meu antigo palestrante costumava dizer ” alta coesão e baixo acoplamento “, ainda é verdade hoje.

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

Eu imagino que seja porque algumas dessas bibliotecas têm dependencies em outras bibliotecas, e se elas ainda não foram vinculadas, você obterá erros de vinculação.