Aula de Laboratório
Criando bibliotecas estáticas e dinâmicas em C/C++
Programação para Software Básico
08/2014
-
Entender o processo de ligação com código-objeto e bibliotecas.
-
Praticar a criação e uso de bibliotecas.
Obs: vocês deve realizar esta aula no terminal do Linux
1 Ligação estática x dinâmica
Como vimos na aula passada, as bibliotecas são incluídas na etapa de ligação (linking), feita normalmente de forma automática pelo compilador.
Há duas formas de ligação:
-
Estática: os módulos são incorporados diretamente no código-objeto, isto é, o programa executável resultante conterá todo o código utilizado de cada módulo.
-
Dinâmica: os módulos são referenciados no código-objeto, mas não incluídos no programa executável.
Esta aula deve ser realizada no Linux, de preferência, no terminal.
1.1 Ligação estática
A ligação estática é o que ocorre normalmente ao compilarmos um programa com vários módulos (exemplo: várias classes, separadas em arquivos diferentes). Cada código-fonte é compilado e produz um arquivo com código objeto equivalente (.c ou .cpp para .o, no caso do Linux).
É possível também ligar bibliotecas de forma estática. Uma biblioteca nada mais é do que uma coleção de módulos, contendo funcionalidades relacionadas (ex: biblioteca de funções matemáticas, biblioteca gráfica, etc). No Linux, bibliotecas estáticas são arquivos com o sufixo .a (de archive) e são criadas com o aplicativo ar (detalhes mais adiante).
As bibliotecas estáticas normalmente ficam nos diretórios /usr/lib ou /usr/local/lib. Por exemplo, verifique o conteúdo de /usr/lib:
Se a biblioteca já estiver no diretório /usr/lib, você pode especificá-la na linha de comando de forma mais simples, com a opção -l (letra “ele”, não "um"):
gcc -o programa programa.c -lz
A opção -l faz com que o ligador procure a biblioteca com o prefixo lib..., ou seja, no caso acima ele irá procurar em /usr/lib/libz.a
É possível também especificar um ou mais diretórios adicionais de busca, com a opção -L:
gcc -o programa programa.c -L/usr/local/lib -lgraphics
A linha acima irá procurar a libgraphics.a também em /usr/local/lib.
1.2 Criando uma biblioteca estática
Para criar uma biblioteca estática, utiliza-se a seguinte linha de comando:
ar rcs minhalib.a modulo1.o modulo2.o modulo3.o ...
Onde minhalib.a é o nome do arquivo resultante e modulo*.o são os diversos módulos a ser inseridos na biblioteca.
Uma vez criada a biblioteca, basta especificá-la na linha de comando do compilador:
gcc -o programa programa.c minhalib.a
Considerando que minhalib.a e programa.cpp estejam no mesmo diretório, esse comando irá utilizar a biblioteca durante a fase de ligação. Observação: mesmo assim, para compilar o programa é necessário ter acesso aos headers (.h) utilizados pela biblioteca.
Exercício 1:
Faça o download do arquivo
glshapes.zip - é uma implementação completa de uma biblioteca de funções para desenhar figuras. O projeto contém um
Makefile, então para compilar o código digite
make no terminal. Resista à tentação de usar o
Code::Blocks!
A partir dessa implementação, crie uma biblioteca estática da seguinte forma:
-
Compile uma vez toda a aplicação com o comando make:
-
Como já sabemos, isso gera os arquivos *.o (códigos-objetos, não executáveis) de todos os módulos. Como queremos criar uma biblioteca para as funções de desenho, usamos agora o comando ar:
ar rcs libshapes.a shapes.o
-
Para visualizar o conteúdo de uma biblioteca, use:
Observe que cada módulo incluído aparece nessa listagem. Também lembre-se de um detalhe importante: só serão incluídos no código-objeto final os módulos realmente utilizados pelo programa. Isso significa que quanto mais modular for a biblioteca, melhor (dentro dos limites do bom senso, é claro). Por exemplo, se houver módulos muito grandes, o uso de uma única função ou classe do módulo faz com que o código do módulo inteiro seja incorporado. Isso tende a criar executáveis maiores (acontece muito no Windows).
Agora que você já sabe fazer como os homens (e mulheres) de verdade faziam antigamente, pode também usar o Code::Blocks, que automatiza boa parte do processo. Para tanto, basta criar um projeto do tipo static library.
Exercício 2:
-
Altere o Makefile, de forma que o projeto passe a usar a biblioteca ao invés de compilar tudo novamente. Nesta linha:
LDFLAGS = -lGL -lGLU -lglut -lm
Troque para:
LDFLAGS = -lGL -lGLU -lglut -lm -L.
Relembrando: a opção -L informa ao ligador um caminho adicional para procurar bibliotecas, no caso, o diretório corrente (“.”).
Também é possível automatizar a criação da biblioteca dentro do próprio Makefile. Para isso, acrescente o código-fonte da biblioteca, como demonstrado a seguir:
-
Primeiro, remova o módulo de desenho da linha abaixo:
-
Agora, acrescente a linha abaixo, que define os módulos a serem incorporados na biblioteca:
-
Duplique a linha que começa com OBJECTS... e troque o nome para OBJECTS2:
OBJECTS2 = $(OBJECTS2:.c=.o)
-
Na regra que define o executável (glshapes), troque para o seguinte:
shapes: $(OBJECTS) libshapes.a
gcc $(CFLAGS) $(OBJECTS) -o shapes -l shapes $(LDFLAGS)
Isso faz com que o executável também dependa da existência da biblioteca.
-
E finalmente, acrescente as seguintes linhas, que definem a regra para criação da biblioteca.
libshapes.a: ${OBJECTS2}
ar rcs libshapes.a ${OBJECTS2}
Dessa forma, ao digitar make no terminal, a biblioteca será automaticamente compilada. Obs: cuide para que antes de "ar rcs..." tenha um caractere TAB: esse formato é obrigatório!
Se quiser, faça o mesmo no Code::Blocks: basta incluir esses parâmetros no linker options (i.e. em caminhos para bibliotecas e bibliotecas adicionais).
1.3 Ligação dinâmica
Atualmente, a forma mais comum do uso de bibliotecas é através de ligação dinâmica. Isso significa que o código executável (programa) não irá mais conter todo os módulos utilizados de cada biblioteca, mas apenas uma referência para eles. No momento da execução, o sistema operacional identifica essas referências e carrega as bibliotecas necessárias, automaticamente. Isso torna o código menor, e permite que a implementação interna das bibliotecas sofra alterações, sem que seja necessário recompilar todos os programas que dependem dela. Isso também tem um efeito colateral benéfico: o sistema consegue manter uma cópia de cada biblioteca utilizada na memória, e compartilha ela com todos os programas que a utilizam.
Você já usou ligação dinâmica: as bibliotecas GL, GLU e glut, utilizadas no exemplo anterior, são todas dinâmicas. Mas como identificar isso ?
Existe um comando que permite identificar as ligações dinâmicas de um executável, denominado ldd. Por exemplo, para visualizar as ligações dinâmicas do programa, experimente:
Você verá uma saída semelhante à seguinte:
linux-vdso.so.1 => (0x00007fff75c5e000)
libGL.so.1 => /usr/lib/x86_64-linux-gnu/mesa/libGL.so.1 (0x00007fce04ae1000)
libglut.so.3 => /usr/lib/x86_64-linux-gnu/libglut.so.3 (0x00007fce0489b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fce044db000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fce041df000)
libglapi.so.0 => /usr/lib/x86_64-linux-gnu/libglapi.so.0 (0x00007fce03fb9000)
...
Ou seja, o programa depende das bibliotecas libGL.so.1, libGLU.so.1, ... O sufixo .so significa shared object, ou seja, objeto compartilhado. Repare que o comando também informa a localização das bibliotecas, o que é uma informação muito útil.
No Mac OSX, o sufixo é .dylib (dynamic library), e deve-se usar a ferramenta otool para obter a mesma informação:
Resultando em:
shapes:
@executable_path/../Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
@executable_path/../Frameworks/GLUT.framework/Versions/A/GLUT (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 15.0.0)
/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.10)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.42.0)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 38.0.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 751.53.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1038.35.0)
...
O equivalente no Windows são os arquivos .dll (dynamic link library).
1.4 Criando uma biblioteca dinâmica
Para criar uma biblioteca dinâmica, utiliza-se o próprio gcc. Acrescente as seguintes linhas ao Makefile:
libshapes2.so: ${OBJECTS2}
gcc -shared -Wl,-soname,libshapes2.so -o libshapes2.so ${OBJECTS2}
A opção -soname... é passada diretamente ao linker (via -Wl), e especifica o nome do arquivo, que também é informado na saída do compilador (-o).
Uma vez criada, a biblioteca dinâmica é utilizada da mesma forma que a estática, ou seja, através da opção -l do compilador.
A última etapa é adicionar uma regra para criar um executável que use a biblioteca dinâmica:
shapes-shared: $(OBJECTS) libshapes2.so
$(CC) $(OBJECTS) -o shapes-shared -lshapes2 $(LDFLAGS)
Observe que dessa forma, o código executável resultante (shapes-shared) fica menor que o gerado no exercício 2.
Exercício 3:
-
Execute o comando:
Se você notou algo estranho na listagem (uma biblioteca faltando), é porque normalmente o diretório corrente não é pesquisado por default para as bibliotecas dinâmicas. Porém, existe uma solução: a variável de ambiente LD_LIBRARY_PATH permite especificar caminhos adicionais para o ligador dinâmico. Por exemplo, digite:
export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH
-
Isso faz com que essa variável receba o diretório corrente (resultado do comando `pwd`), mais os caminhos que já possuía ($LD_LIBRARY_PATH). Agora, execute novamente o comando ldd. O que mudou ?
-
Você pode também visualizar o conteúdo da biblioteca dinâmica em si, através do comando nm:
Esse comando mostra uma lista das funções contidas na biblioteca, entre outras informações (incluindo dependências dinâmicas dela mesma).
Assim como nas bibliotecas estáticas, é possível criar bibliotecas dinâmicas com o Code::Blocks: basta escolher o tipo de projeto shared library.
Para maiores detalhes: