Bibliotecas Estáticas e DLL’s em Linguagem C

bibliotecasc

Muitos desenvolvedores de software com certeza conhecem e já fizeram uso de Bibliotecas Estáticas e/ou DLL’s (Bibliotecas Dinâmicas). Contudo sinto que muitos conceitos (inclusive de empregabilidade) de uma ou de outra não é de tão comum conhecimento assim. Nesse artigo viso compartilhar alguns insights que tive enquanto mantenedor e usuário de algumas Bibliotecas Estáticas e Dinâmicas. Apontarei vantagens e desvantagens no uso de cada tipo, definindo os aspectos de escolha que um arquiteto de soluções em geral leva em consideração. Será um prazer se no final do texto o leitor perceber que tais diferenças podem trazer grandes impactos no processo de manutenção de um software ao longo de toda a sua vida útil.

Algumas Observações Iniciais

Primeiramente é importante frisar que todo conteúdo apresentado nesse artigo foi pensado dentro do mundo do Desenvolvimento de Software codificado em Linguagem C. Embora em essência os conceitos possam ser estendidos, não haverá nenhum cuidado especial sobre especificidades de outras linguagens de programação que venham divergir do presente texto. 

Atualmente na internet existem muitos materiais que tratam o assunto com muita qualidade, explicando de forma rica e detalhada. Entretanto, busco aqui juntar alguns outros pontos que me surpreenderam positivamente na diferença de uso de uma Biblioteca Estática e uma Biblioteca Dinâmica.

O que é uma Biblioteca?

É de comum discernimento que o uso de Bibliotecas objetivam a modularização e a reutilização de um determinado código fonte que cumpre alguma finalidade específica. Entretanto, mais do que isso, as bibliotecas visam também tornar opaca toda lógica/tarefa que constitui esse módulo de software. Sendo assim, o programador da aplicação cliente não precisa se preocupar com os detalhes internos à lógica da biblioteca, mas tão somente se o uso dos recursos disponibilizados pela Lib estão conforme ao especificado pela documentação.

Uma propriedade muito interessante das Bibliotecas, é a estabilidade das interfaces disponibilizadas. Assinaturas de funções, valores retornados e toda a lógica das chamadas serão sempre preservadas. Dessa forma, quando a biblioteca sofrer qualquer tipo de atualização, todos os aplicativos clientes deverão continuar funcionando de acordo. As atualizações em teoria podem adicionar comandos/recursos novos e modificar somente a lógica interna da Biblioteca. Por outro lado, a interface e a lógica de chamada de qualquer função já lançada em produção nunca deve ser modificada. Na prática é exigido muita experiência/expertise para definir-se uma interface suficientemente estável que poderá suprir as mais diversas demandas de uso de aplicações que funcionam de forma distintas uma das outras. Dentro desse cenário, pode-se pensar no conceito de “escalabilidade em software” para o uso de Bibliotecas. Fazendo um paralelo com os servidores web, que devem suportar um extenso número de chamadas instantâneas sem ficar fora do ar, as Bibliotecas devem ser empregadas em um extenso número de diferentes tipos de aplicações, preservando sua generalidade e nunca criando especificidades para cada caso de uso. Atingindo-se tal característica, um software de uma forma geral, viabiliza a saúde da sua evolução no longo prazo.   

O que é uma Biblioteca Estática?   

Quando uma Biblioteca é concebida para ser uma Biblioteca Estática, após o processo de ‘Build’ não existirá um objeto executável final e sim um arquivo importável do tipo .lib, junto com seus respectivos cabeçalhos (.headers). Enquanto o arquivo .lib, conterá todos os “recursos” da biblioteca estática devidamente compilados (mas ainda latentes para serem linkados para a geração do binário final) os arquivos .headers permitirão que tais recursos (funções, variáveis, estruturas, etc) sejam devidamente referenciados pelo aplicativo cliente. Enquanto em ambientes Windows os arquivos importáveis são do formato .lib, em Sistemas Operacionais Unix tais arquivos possuem a extensão .a. 

O processo de build de uma biblioteca estática pode ser ilustrado nas etapas da Figura 1 abaixo.

Bibliotecas Estáticas e Dinâmicas
Figura 1: Etapas de Build de uma Lib Estática
  • Pré-Processamento: Nessa etapa ocorre a resolução das expressões macros (#defines, #ifdefs, etc). No final do processo a saída será o mesmo código fonte, contudo com suas as expressões macros expandidas.
  • Compilação: Quando os módulos pré-processados (arquivos .c do diretório Src) de uma biblioteca estática são compilados, deverão ser gerados um arquivo objeto .o para cada arquivo fonte .c correspondente. Para o compilador gcc por exemplo, basta passar a flag -c como opção de compilação. Assim o compilador entende que não deve ocorrer o link entre todos os arquivos objetos criados, mantendo-os assim modularizados.
  • Archiving: Nessa etapa todos os arquivos .o são agrupados (de forma indexada) em um único arquivo .a ou .lib. Basicamente nessa etapa, todas as chamadas contidas individualmente em cada arquivo objeto serão reunidas de forma ordenada em um único arquivo. Esse fato permite que a linkagem das chamadas das funções e símbolos da biblioteca pela aplicação cliente sejam muito mais rápidas, uma vez que todas as referências se encontram ordenadas a partir de um único endereço de memória, ao invés de se encontrarem em diferentes arquivos espalhados pelo disco de armazenamento.

Os comandos gcc abaixo ilustram um processo de geração de uma Lib Estática:

O processo de build de um aplicativo cliente que faz uso de uma biblioteca estática pode ser ilustrado nas etapas da Figura 2 abaixo.

Bibliotecas Estáticas e Dinâmicas
Figura 2: Etapas de Build de um App Cliente da Static Lib

O comando gcc abaixo ilustra o processo de build de um Aplicativo que faz uso de uma Lib Estática:

É importante notar que todo conteúdo executável referente a biblioteca, estará contido internamente no binário do aplicativo. 

É válido lembrar que as Libs Estáticas podem também serem consumidas por outra Bibliotecas (Estática ou Dinâmica) e não tão somente pela camada da aplicação.

O que é uma Biblioteca Dinâmica?    

As Bibliotecas Dinâmicas também são conhecidas como DLL’s (Dynamic Linked Libraries). Ao contrário das Static Libs, estas possuem um arquivo binário executável como artefato final. O fluxo de build de uma DLL segue basicamente as mesmas etapas de uma aplicação. A diferença conceitual entre os binários de um aplicativo padrão e de uma DLL é a forma como cada um é executado/chamado pelo sistema operacional. Em geral, os aplicativos são executados via mecanismos de bootloader (Mono-Aplicação) ou Menu de Seleção (Multi-Aplicação). Já os binários das DLL’s são utilizados através das API’s disponibilizadas dentro da camada de aplicação.

Um aplicativo cliente pode fazer uso dos recursos de uma DLL de diversas formas. Os seguintes steps ilustram uma forma “Windows way to do so“.

  1. Abrir a Biblioteca;
  2. Atribuir os endereços de memória das funções, variáveis e estruturas aos ponteiros locais da aplicação.
  3. Fazer uso da Biblioteca via manipulação dos ponteiros.
  4. Fechar a Biblioteca.

O seguinte projeto carrega as funções “equal”, “greaterThan” e “lessThan” da DLL “compare.dll” para fazer operações de comparações entre números inteiros.

Código dllLoader.h

Código dllLoader.c

Embora os aplicativos clientes possam fazer chamadas como as do código acima diretamente, é prática interessante encapsular tais chamadas dentro de uma biblioteca estática. Dessa forma o acesso aos recursos da DLL seria muito mais modular e fácil de serem utilizados. No final o aplicativo faria uso de uma interface estável que seria consumida através de uma biblioteca estática e em caso de atualizações bastaria carregar o novo executável referente a DLL. O leitor poderá tomar como exemplo o projeto DllTutorial que pode ser acessado no Github .

Bibliotecas Estáticas x Bibliotecas Dinâmicas

Nesta seção buscarei listar os principais pontos que podem motivar um arquiteto de soluções escolher entre conceber uma Biblioteca como sendo Estática ou Dinâmica.

Dependências (DLL’s Hell Problem)

A parte executável referente às chamadas da Biblioteca Estática se encontram internos ao binário da aplicação. Já os binários da DLL se encontram desacoplados em relação aos da aplicação. Essa diferença pode trazer consequências drásticas ao longo do ciclo de vida útil de um software, entre elas podemos citar:

  1. Aplicações compiladas com Bibliotecas Estáticas diminuem a complexidade de gerenciamento dos pacotes carregados na plataforma target. Nesses casos o versionamento da Lib se encontrará atrelado a versão de release da própria aplicação.
  2. Em sistemas que usam DLL’s é necessário se atentar além da versão da aplicação, ao versionamento de todas as DLL’s utilizadas. Vale lembrar que em sistemas grandes e complexos é comum que DLL’s dependam de outras DLL’s. Mesmo quando os mantenedores do sistema documentam uma concisa e clara árvore de dependências, é comum ainda que a expressão “DLL’s Hell Problem” venha fazer sentido tardiamente a todos os integrantes do time, sejam eles Supporters, QA’s, Dev’s ou Gerentes .
  3. Caso as DLL’s não forem carregadas ou sofrerem algum comportamento imprevisto, a aplicação deixará de funcionar de acordo. Em sistemas de alto grau de complexidade, até mesmo os desenvolvedores mais seniores podem chegar a ficar completamente perdidos, quando o bad-behaviour ocorre dentro de um único componente da árvore de dependências.

Corrupção de Memória

  1. Sistemas que fazem uso de DLL’s são mais suscetíveis a corrupção de memória. Para justificar isso, basta pensar de forma simplória que todo binário possui uma probabilidade p(x) de ter sua memória corrompida. Com a fragmentação do sistema em um número N de Dll’s, tal chance será elevada à N.p(x).
  2. O preenchimento do mapa de memória do Sistema será mais complexo, o que aumenta significativamente os riscos de memory leak.

Bug Tracking

A depuração de um bug contido no interior de uma biblioteca dinâmica pode ser um processo sofrido. É certo que o processo será computacionalmente mais pesado, uma vez que o debugger precisará monitorar o estado de mais de um binário ao mesmo tempo. Além disso, existem situações em que esse “peso” interfere diretamente na reprodução da falha reportada. Como dito anteriormente, as Bibliotecas podem ser utilizadas nos mais variados tipos de sistemas com os mais diversos tipos de arquiteturas e configurações distintas. É fato que os bugs mais difíceis de serem rastreados ocorrem geralmente em aplicativos clientes que possuem uma arquitetura de software não tão convencionais. 

Certificação de Software

Sempre que uma Biblioteca exigir alguma atualização, softwares que fazem uso de Libs Estáticas necessitarão recompilar o binário final. Esse fato pode trazer consequências críticas para a manutenção do sistema dependendo do tipo de software em questão.

Em Softwares que exigem processos de certificação, toda a aplicação que tiver seus binários alterados deverão ser re-certificadas. Logo, todas as vezes que a biblioteca sofrer alguma atualização, o software deverá sofrer um novo processo de certificação, operação que pode ser demasiadamente custosa para o proprietário da solução. Por outro lado todo o processo de certificação, pode ser exclusivamente delegado ao binário da DLL. Dessa forma, sempre que a biblioteca demandar atualizações, a empresa mantenedora da DLL terá o ônus da certificação. Bastará para a aplicação cliente “carregar” o novo binário já certificado, não exigindo alteração alguma do lado da aplicação cliente.

Armazenamento

Dentro do contexto de uma plataforma multi-aplicação a biblioteca estática leva desvantagem contra o uso de DLL’s. Se todos os aplicativos da plataforma se utilizam da mesma Static Lib, uma “cópia” exclusiva da Lib será incorporada no binário de cada aplicação. Dependendo do número de aplicações clientes na plataforma (Um sistema operacional por exemplo), tal abordagem é impraticável do ponto de vista de uso de memória de programa. Por outro lado as DLL’s sempre terão uma única cópia de seu binário na plataforma, sendo a mesma compartilhada entre todos os aplicativos presentes (motivo ao qual DLL’s também são chamadas de Shared Libraries). 

Desempenho 

Chamadas de funções de Bibliotecas Estáticas são executadas mais rapidamente comparada as Bibliotecas Dinâmicas. O motivo se dá pelos recursos da Lib Estática estar presente dentro do mesmo binário da aplicação. Logo o processo de execução é rápido. Já em Libs Dinâmicas, todos os recursos da Lib se encontram em um binário externo a aplicação, o que do ponto de vista de arquitetura de computadores torna a execução de qualquer chamada mais burocrática comparado às Static Libs.

Mas nem tudo é um mar de rosas…

Apesar do artigo ter defendido uma série de conceitos que as Bibliotecas em teoria deveriam trazer para o projeto de engenharia de um software, sabemos que na prática as dores encontradas são outras. A mais óbvia delas é a ideia de que a aplicação cliente não sofrerá alteração quando alguma biblioteca for atualizada. Todo desenvolvedor que contar com tal característica, fatalmente em algum momento irá se frustar. Ocorre que é muito difícil qualquer módulo de software evoluir, sem levar algum tipo de alteração para as camadas dependentes. Caso a arquitetura do sistema estiver bem implementada, as alterações do lado da Biblioteca nunca deverão produzir erros de compilação ou em tempo de execução. Já para o aplicativo cliente conseguir acompanhar a evolução da Biblioteca, as alterações nessa camada deverão ser sistemáticas e aditivas. Por fim o que foi aqui exposto é tão verdade, que em geral os processos de certificação levam em conta essa problemática. Nessa lógica, os módulos críticos (DLL’s) em geral sofrem um processo de certificação mais rigoroso e burocrático, enquanto as camadas de aplicação necessitem de uma certificação mais leve e menos custosa. 

Saiba mais

Estilo de código – Boas práticas de programação em linguagem C

Modificadores de Acesso na Linguagem C

Orientação a objeto em C: Encapsulamento com estruturas opacas

Referências

https://medium.com/@StueyGK/static-libraries-vs-dynamic-libraries-af78f0b5f1e4#:~:text=Libraries%2C%20like%20functions%20also%20save,outside%20of%20the%20executable%20file.

https://medium.com/@StueyGK/what-is-c-static-library-fb895b911db1

https://medium.com/@StueyGK/from-fingers-to-metal-a65dcfda7116

https://medium.com/@romalms10/why-dynamic-libraries-bbaa55b199db#:~:text=into%20the%20executable-,Dynamic%20Libraries,be%20loaded%20anywhere%20in%20memory.

https://en.wikipedia.org/wiki/Dynamic-link_library

https://www.geeksforgeeks.org/difference-between-static-and-dynamic-memory-allocation-in-c

https://rosettacode.org/wiki/Call_a_function_in_a_shared_library

https://www.cs.swarthmore.edu/~newhall/unixhelp/howto_C_libraries.html

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software
Comentários:
Notificações
Notificar
guest
4 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Matheus Souza
Matheus
18/01/2021 11:01

Muito bom!

Leandro Poloni Dantas
Membro
18/01/2021 08:10

Excelente trabalho Caio. Precisamos de mais trabalhos como esse. Onde é feito um mergulho no mais profundo nos fundamentos. Parabéns!

Mercenário
Mercenário
15/01/2021 23:50

Gostei do artigo, muito bem escrito. Apesar de ser voltado para linguagem C, é facil de se estender a problemática para qualquer linguagem.

Rubens Junior
15/01/2021 08:38

Muito bom artigo.

Talvez você goste:

Nenhum resultado encontrado.

Séries

Menu

EVENTO ONLINE

Simplificando seus projetos de Internet das coisas com o iMCP HT32SX Sigfox

DATA: 18/05 às 15:00h