Como estruturar funções C em uma biblioteca de utilitários reutilizáveis

12

Você escreveu algum código. Funciona. Mas agora você quer usar aquele gerador rand() ou aquela rotina bubble_sort em um projeto completamente diferente. Copiar e colar é preguiçoso. Também é frágil. Ao atualizar uma versão, você precisa procurar todas as cópias do código.

A mudança profissional? Construa uma biblioteca.

No ecossistema C, uma biblioteca não é uma caixa preta mágica. São apenas dois arquivos com funções diferentes. Você precisa de um arquivo de cabeçalho (.h ) e um arquivo de origem (.c ). O cabeçalho informa ao resto do mundo quais funções existem. O arquivo de origem realmente faz o trabalho.

Construindo a Interface: util.h

O arquivo de cabeçalho atua como o contrato. Não contém a lógica. Ele contém as promessas. Constantes, tipos e protótipos de funções.

Crie um arquivo chamado util.h.

Essas palavras-chave externas estão fazendo um trabalho pesado. Eles dizem ao compilador: “Não procure a implementação aqui. Vou encontrá-la mais tarde, durante a vinculação”. Se você estiver preso a um compilador legado anterior aos padrões C modernos, retire os parâmetros de bubble_sort. Compiladores antigos são exigentes assim.

Escondendo o maquinário: util.c

Agora, para o código real. Salve como util.c.

Observe a diretiva include. Usamos aspas ("util.h" ) em vez de colchetes angulares ( ). A diferença importa. As cotações aparecem em seu diretório atual. Os colchetes angulares aparecem nos diretórios do sistema. Este arquivo é local. Pertence aqui.

Aqui está a parte sutil: rand_seed. Ele fica em util.c. não está em util.h. Esta é uma informação escondida em ação. O código que usa esta biblioteca pode chamar rand(), mas não pode tocar a semente diretamente. O estado é encapsulado. Para uma aplicação estrita, você pode colocar a palavra-chave static na frente da definição da variável. Isso bloqueia tudo. Sem acesso externo. Período.

Usando a biblioteca: main.c

Agora, escreva o programa que o utiliza. Salve como main.c.

Mais curto? Sim. Mais limpo? Absolutamente. O programa principal não se importa como rand() gera números. Exige apenas um número. Essa separação é o ponto principal.

Compilando e vinculando

Como você transforma esses arquivos separados em um executável em execução? Você segue a dança de três passos.

Primeiro, compile o código-fonte da biblioteca em um arquivo objeto. Supondo que você esteja em um sistema semelhante ao UNIX usando GCC:

O sinalizador -c não é negociável aqui. Diz ao compilador para parar após produzir o arquivo objeto. Ele cria util.o. Este arquivo contém código de máquina, mas está incompleto. Não tem função principal. Ele não pode funcionar sozinho. É um componente, não um produto.

A seguir, compile seu programa principal:

Isso gera main.o. Novamente, apenas código de máquina. Ainda preso sem a lógica da biblioteca.

Finalmente, vincule-os.

Este comando pega os dois arquivos objeto e os une em um único executável chamado main. Agora você tem um programa completo. Execute-o com ./main (ou apenas main se você estiver no Windows ou tiver em seu caminho).

São muitas etapas para duas funções. Mas imagine fazer isso para um projeto com cinquenta arquivos. Você compila cada arquivo .c uma vez. Você os vincula. Alterar uma função na biblioteca? Recompile apenas esse arquivo. Vincule novamente. O resto permanece intocado.

Makefiles automatizam isso