Programas com múltiplos arquivos
Esta aula ensina como organizar programas em C em múltiplos arquivos, cobrindo a separação em módulos, o uso da palavra-chave extern, a compilação conjunta e o processo de linkedição. O aluno aprenderá a criar projetos modulares, compilar com gcc e entender o papel do linker.
Em projetos reais, programas em C raramente são escritos em um único arquivo. A medida que o código cresce, manter tudo em um arquivo torna-se inviável: dificulta a manutenção, a reutilização e o trabalho em equipe. A solução é dividir o programa em múltiplos arquivos, cada um responsável por uma parte específica da funcionalidade. Essa prática é chamada de modularização e é essencial para o desenvolvimento de software profissional.
Nesta aula, vamos explorar como separar o código em módulos, como compartilhar variáveis e funções entre arquivos usando extern, como compilar vários arquivos juntos e o que acontece durante a linkeditação (linkagem). Ao final, você terá uma base sólida para organizar seus próprios projetos em C de forma eficiente.
Separando em módulos
A modularização em C é feita criando-se arquivos de código-fonte (.c) e arquivos de cabeçalho (.h). Os arquivos .c contêm a implementação das funções e variáveis, enquanto os arquivos .h declaram as interfaces públicas, ou seja, os protótipos das funções e a declaração de variáveis globais que podem ser usadas por outros módulos.
Vamos criar um exemplo simples: um programa que calcula a área de um círculo. Teremos um módulo circulo.c com a implementação da função area_circulo, e um arquivo circulo.h com a declaração dessa função. O arquivo principal main.c usará essa função.
// circulo.h
#ifndef CIRCULO_H
#define CIRCULO_H
#define PI 3.14159
double area_circulo(double raio);
#endif
// circulo.c
#include "circulo.h"
#include
double area_circulo(double raio) {
return PI * raio * raio;
}
// main.c
#include
#include "circulo.h"
int main() {
double r = 5.0;
printf("Área do círculo de raio %.2f: %.2f\n", r, area_circulo(r));
return 0;
}
Observe o uso de include guards (#ifndef, #define, #endif) no arquivo circulo.h. Isso evita que o cabeçalho seja incluído mais de uma vez, o que causaria erros de redefinição.
extern
A palavra-chave extern é usada para declarar uma variável ou função que é definida em outro arquivo. Ela informa ao compilador que a entidade existe, mas sua definição será encontrada durante a linkedição. Isso é útil para compartilhar variáveis globais entre módulos.
Por exemplo, suponha que tenhamos um arquivo config.c que define uma variável global int debug = 1; e queremos usá-la em main.c. Precisamos declarar extern int debug; em main.c (ou em um cabeçalho).
// config.c
int debug = 1;
// main.c
#include
extern int debug;
int main() {
if (debug) {
printf("Modo debug ativo\n");
}
return 0;
}
É importante distinguir declaração de definição. Uma declaração com extern não aloca memória; apenas anuncia a existência da variável. A definição ocorre em um único arquivo e aloca memória. Se você definir a mesma variável global em mais de um arquivo, o linker acusará erro de símbolo duplicado.
Compilando juntos
Para compilar um programa com múltiplos arquivos usando o GCC, você pode passar todos os arquivos .c de uma só vez:
gcc main.c circulo.c config.c -o programa
O GCC compila cada arquivo separadamente e depois os linkedita. Você também pode compilar separadamente para gerar arquivos-objeto (.o) e depois linká-los, o que é útil para projetos grandes:
gcc -c main.c -o main.o
gcc -c circulo.c -o circulo.o
gcc -c config.c -o config.o
gcc main.o circulo.o config.o -o programa
Compilar separadamente permite recompilar apenas os módulos alterados, economizando tempo em projetos grandes. Muitos projetos usam ferramentas como make ou CMake para automatizar esse processo.
Linkedição
A linkedição (ou linkagem) é a etapa que combina os arquivos-objeto em um executável final. O linker resolve referências entre símbolos (funções e variáveis) definidos em diferentes módulos. Se uma função é chamada em main.c mas definida em circulo.c, o linker conecta a chamada à definição.
Erros comuns de linkedição incluem:
- Referência indefinida: quando uma função ou variável declarada com
externnão tem definição em nenhum arquivo objeto. - Definição múltipla: quando a mesma variável global é definida em mais de um arquivo (sem usar
externcorretamente). - Falta de bibliotecas: quando funções como
sqrtexigem a biblioteca matemática (-lmno GCC).
O comando gcc já chama o linker automaticamente quando você passa múltiplos arquivos ou objetos. Para bibliotecas externas, você usa -l (por exemplo, -lm para math).
Boas práticas
- Use cabeçalhos para declarar a interface pública de cada módulo.
- Evite variáveis globais sempre que possível; prefira passar parâmetros.
- Se precisar de variáveis globais compartilhadas, declare-as com
externnos cabeçalhos e defina-as em apenas um arquivo.c. - Use include guards em todos os cabeçalhos.
- Compile com
-Walle-Wextrapara capturar avisos.
Referências
- Documentação oficial do GCC
- Storage duration e extern - cppreference
- IBM: linkage e extern
- Learn-C: Múltiplos arquivos
- GeeksforGeeks: múltiplos arquivos em C
Exercícios
- Crie um módulo
calculadora.hecalculadora.cque declare e defina as funçõessoma,subtrai,multiplicaedivide. Escreva ummain.cque use essas funções para calcular a expressão(10 + 5) * 2 / 3e imprima o resultado.✓ Resposta:// calculadora.h #ifndef CALCULADORA_H #define CALCULADORA_H double soma(double a, double b); double subtrai(double a, double b); double multiplica(double a, double b); double divide(double a, double b); #endif// calculadora.c #include "calculadora.h" double soma(double a, double b) { return a + b; } double subtrai(double a, double b) { return a - b; } double multiplica(double a, double b) { return a * b; } double divide(double a, double b) { return a / b; }// main.c #include#include "calculadora.h" int main() { double resultado = divide(multiplica(soma(10, 5), 2), 3); printf("Resultado: %.2f\n", resultado); return 0; } - Defina uma variável global
int contadorem um arquivocontador.c. Em outro arquivomain.c, declare-a comexterne incremente-a em um loop de 1 a 10, imprimindo o valor a cada iteração.✓ Resposta:// contador.c int contador = 0;// main.c #includeextern int contador; int main() { for (int i = 0; i < 10; i++) { contador++; printf("Contador: %d\n", contador); } return 0; } - Compile os arquivos do exercício 1 separadamente (gerando arquivos-objeto) e depois linke-os. Escreva os comandos do GCC.
- Explique o que é uma referência indefinida e dê um exemplo de código que cause esse erro.
- Escreva um programa com três módulos:
media.h/media.c(função que calcula a média de um vetor),imprime.h/imprime.c(função que imprime um vetor), emain.cque lê 5 números do usuário, armazena em um vetor, calcula a média e imprime o vetor e a média.
gcc -c main.c -o main.o
gcc -c calculadora.c -o calculadora.o
gcc main.o calculadora.o -o programa
void funcao(); em um arquivo, mas não define em nenhum outro, o linker retornará erro. Exemplo:// main.c
void funcao();
int main() { funcao(); return 0; }
Ao compilar com gcc main.c, o linker não acha funcao e emite erro.// media.h
#ifndef MEDIA_H
#define MEDIA_H
double media(int vet[], int n);
#endif
// media.c
#include "media.h"
double media(int vet[], int n) {
int soma = 0;
for (int i = 0; i < n; i++) soma += vet[i];
return (double)soma / n;
}
// imprime.h
#ifndef IMPRIME_H
#define IMPRIME_H
void imprime_vetor(int vet[], int n);
#endif
// imprime.c
#include
#include "imprime.h"
void imprime_vetor(int vet[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", vet[i]);
}
printf("\n");
}
// main.c
#include
#include "media.h"
#include "imprime.h"
int main() {
int vet[5];
printf("Digite 5 números:\n");
for (int i = 0; i < 5; i++) {
scanf("%d", &vet[i]);
}
printf("Vetor: ");
imprime_vetor(vet, 5);
printf("Média: %.2f\n", media(vet, 5));
return 0;
}