c++ - world - make gcc tutorial




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

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

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


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 função main() , dependente do libmysqlclient
  • libmysqlclient - static, por causa do exemplo (você prefere a biblioteca compartilhada, é claro, já que o libmysqlclient é enorme); em /usr/local/lib ; e dependente de coisas da libz
  • libz (dinâmico)

Como ligamos isso? (Nota: exemplos da compilação 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

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 / variáveis ​​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.

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

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


O linkador GNU ld é um chamado linkador inteligente. Ele rastreará 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 linker típico do UNIX funciona da esquerda para a direita, então coloque todas as suas bibliotecas dependentes à esquerda e aquelas que satisfazem essas dependências à 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!


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

No Qt isso significa adicionar:

QMAKE_LFLAGS += -Wl,--start-group

Economiza muito tempo mexendo e não parece diminuir muito a ligação (o que leva muito menos tempo do que a compilação de qualquer maneira).


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. Apenas falhará o link com símbolos indefinidos se a ordem da biblioteca estiver errada.

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


(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 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 anota os símbolos não resolvidos. Se uma biblioteca resolve o símbolo, ele pega os arquivos de objetos daquela biblioteca 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 biblioteca anterior, haverá um ciclo. Você pode resolver isso colocando as bibliotecas dependentes de ciclos por -( e -) , como -( -la -lb -) (talvez seja necessário escapar dos parens, como -\( e -\) ). O vinculador, então, pesquisa essas libras incluídas várias vezes para garantir que as dependências 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 objeto do programa. A diferença aqui com as bibliotecas estáticas é que você não deve se preocupar com as dependências das bibliotecas umas contra as outras, porque as bibliotecas dinâmicas resolvem suas próprias dependências .

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 objeto 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 dependências do b .

Aqui está um exemplo das implicações se você perder a especificação das 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ê olhar agora para quais dependências o binário tem, observe 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.







linker