Em que ordem as bibliotecas estáticas devem ser linkadas?
Após me deparar com alguns erros do linker, resolvi entender realmente o que eu estava fazendo ao linkar bibliotecas estáticas. Na verdade, o termo linkar talves não esteja totalmente correto, pois, ao gerar uma biblioteca estática, estamos fazendo nada mais do que juntar mais de um arquivo objeto em um único pacote, portanto, não estamos “linkando” a biblioteca, e sim carregando os símbolos nela dispostos.
No momento de linkar a biblioteca ao executável, o linker irá procurar em um arquivo (biblioteca) apenas uma vez, de acordo com a ordem passada na linha de comando.
Vejamos a um exemplo para facilitar a compreensão.
Vamos tomar como exemplo libA, libB e libC.
a.cpp
#include <iostream> using namespace std; void printA() { cout << "A" << endl; }
b.cpp
#include <iostream> using namespace std; extern void printA(); void printB() { printA(); cout << "B" << endl; }
c.cpp
#include <iostream> using namespace std; extern void printB(); void printC() { printB(); cout << "C" << endl; }
Perceba que o arquivo fonte “a.cpp” é o mais básico (menor), e não depende de nenhum símbolo externo. Já o fonte “b.cpp” e “c.cpp” dependem de símbolos presentes em “a.cpp” e “b.cpp”, respectivamente.
Compilando:
g++ -c a.cpp
g++ -c b.cpp
g++ -c c.cpp
Após compilar os fontes acima, serão gerados os arquivos objetos de cada um. Agora vamos empacotar cada um em uma biblioteca estática.
ar -r libA.a a.o
ar -r libB.a b.o
ar -r libC.a c.o
Pronto, já temos nossas bibliotecas estáticas prontas para o uso. Vamos agora ver como ficaria o programa principal para utilizar estas bibliotecas:
main.cpp
extern void printC(); int main() { printC(); return 0; }
Perceba novamente, que o arquivo fonte “main.cpp” utiliza apenas a função “printC()”, que está declarada como externa. Agora vamos à compilação, e entender a “moral da história” que deu origem a este post.
Compilando:
g++ -o main main.cpp -lA -lB -lC -L.
Ao executar a linha de comando acima, o linker acusa o seguite:
./libC.a(c.o): In function `printC()':
c.cpp:(.text+0x7b): undefined reference to `printB()'
collect2: ld returned 1 exit status
E agora? O que muita gente não sabe (e eu também não sabia) é que o linker não carrega todos os símbolos presentes na biblioteca estática. O que ele faz, é, respeitando a ordem dos parâmetros de linha de comando, carregar os símbolos que estão definidos e sendo usados em cada arquivo fonte. Caso ele encontre algum símbolo (função) que ele desconheça, ele marca-o como “indefinido”, deixando para efetuar o carregamento depois. Mas perceba que o linker irá carregar da biblioteca estática apenas os símbolos utilizados anteriormente, ou seja, aqueles que foram marcados como “indefinidos”.
Caso alguma biblioteca passada como argumento na linha de comando após aquela que define determinado símbolo, solicite a inclusão do mesmo (símbolo), não fará com que o linker retorne na biblioteca anterior e o carregue.
É aí que está a grande confusão. Eu sempre imaginava o contrário disso, ou seja, que a ordem correta dos parâmetros deveria ser, definir primeiro os símbolos utilizados pelas bibliotecas subsequentes.
MAS NÃO! DEVEMOS FORNECER AO LINKER AS BIBLIOTECAS NA ORDEM DE MAIOR PARA A MENOR. Ou seja, fornecer primeiro as bibliotecas que utilizam os símbolos, para depois fornecer aquelas que o definem, pois o linker carrega somente os símbolos utilizados anteriormente.
Desta forma, conforme visto no exemplo acima, o linker acusou “undefined reference”, pois a “libC” utiliza o símbolo “printB()” que, na ordem na linha de comando, marcou-o com “indefinido” e nenhuma biblioteca subsequente o definiu.
O que aconteceu foi que o linker verificou os símbolos solicitados no arquivo fonte “main.cpp”. Lá ele percebeu o uso da função externa “printC()”, que até então não havia sido definida, e marcou-a como “indefinida”. Passando a verificar a “libA.a”, ele verificou todos os símbolos presentes nela, e constatou que nenhum havia sido utilizado até então. Portanto, não carregou nenhum símbolo. Na sequência, foi verificado a “libB.a”, que também não possuia nenhum símbolo utilizado, fazendo com que o linker novamente não carregasse nenhum símbolo. Por fim, o linker verificou a biblioteca “libC.a”, percebendo que o símbolo “printC()” estava sendo referenciado (pelo “main.cpp”), e estava marcado como “indefinido”. Desta forma, ele carregou o símbolo “printC()”. Porém, a função “printC()” utiliza o símbolo “printB()”, que até então, não foi definido. Neste ponto, o linker finalizou a verificação em todas as bibliotecas informadas, e percebeu que o símbolo “printB()” continua indefinido após a verificação de todas as bibliotecas. Portanto, está aí o motivo do “undefined reference” visto acima.
Uma compilação correta seria:
g++ -o main main.cpp -lC -lB -lA -L.
Fazendo, desta forma a com que o linker enxergue os símbolos que estão sendo usados dentre as bibliotecas fornecidas.
Sei que a explicação parece um pouco confusa, e talves eu não tenha conseguido ser claro o suficiente. Mesmo assim, vale como uma base para quem precisar.
Tags: C/C++, linker, Linux, static library
You can comment below, or link to this permanent URL from your own site.