Programação com Múltiplos Arquivos
Programação com múltiplos arquivos é como colaboração em equipe — declarações são contratos (arquivos de cabeçalho), implementações são o trabalho (arquivos fonte), extern é uma referência entre departamentos, e Makefile é o cronograma do projeto.
Por Que Múltiplos Arquivos
Um único arquivo .c é suficiente para programas pequenos, mas projetos reais têm bases de código grandes que precisam ser divididas por funcionalidade em múltiplos arquivos:
- Lógica clara com responsabilidades bem definidas
- Compilação mais rápida — alterar um arquivo recompila apenas aquele arquivo
- Reutilização de código — módulos comuns podem ser compartilhados entre projetos
- Colaboração em equipe — diferentes pessoas são responsáveis por diferentes módulos
Escrevendo Arquivos de Cabeçalho
Arquivos de cabeçalho (.h) contêm declarações, e arquivos fonte (.c) contêm definições. Esta é a regra mais fundamental de modularização em C.
Conteúdo do Arquivo de Cabeçalho
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
int subtract(int a, int b);
double average(int *arr, int n);
#endif
Conteúdo do Arquivo Fonte
#include "math_utils.h"
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
double average(int *arr, int n) {
if (n <= 0) return 0.0;
int sum = 0;
for (int i = 0; i < n; i++) sum += arr[i];
return (double)sum / n;
}
Programa Principal
#include <stdio.h>
#include "math_utils.h"
int main(void) {
printf("3 + 5 = %d\n", add(3, 5));
printf("10 - 4 = %d\n", subtract(10, 4));
int scores[] = {80, 90, 75, 88};
printf("Media: %.1f\n", average(scores, 4));
return 0;
}
extern para Referências Entre Arquivos
Uma variável global definida em um arquivo .c pode ser acessada de outros arquivos usando extern.
Definição (em algum arquivo .c)
int g_count = 0;
Declaração (em um cabeçalho ou outro arquivo .c)
extern int g_count;
extern diz ao compilador "esta variável está definida em outro lugar — esta é apenas uma declaração", então nenhum novo espaço é alocado.
Exemplo
Estrutura do projeto:
project/
├── counter.h
├── counter.c
└── main.c
counter.h:
#ifndef COUNTER_H
#define COUNTER_H
extern int g_count;
void increment(void);
int get_count(void);
#endif
counter.c:
#include "counter.h"
int g_count = 0;
void increment(void) {
g_count++;
}
int get_count(void) {
return g_count;
}
main.c:
#include <stdio.h>
#include "counter.h"
int main(void) {
increment();
increment();
increment();
printf("count = %d\n", get_count());
printf("g_count = %d\n", g_count);
return 0;
}
count = 3
g_count = 3
extern. extern int g_count = 0; pode se tornar uma definição em alguns compiladores, causando erros de definição duplicada.
Comandos de Compilação Multi-Arquivo
Compilar Individualmente, Depois Ligar
gcc -c counter.c -o counter.o
gcc -c main.c -o main.o
gcc counter.o main.o -o myapp
A flag -c compila sem ligar, produzindo arquivos objeto .o. Todos os arquivos .o são então ligados no executável final.
Compilar Tudo de Uma Vez
gcc counter.c main.c -o myapp
Simples, mas alterar qualquer arquivo requer recompilar tudo — não adequado para projetos grandes.
main.c, só precisa de gcc -c main.c e depois ligar, sem recompilar counter.c.
Básico de Makefile
Makefile automatiza regras de compilação e só recompila arquivos que foram modificados.
CC = gcc
CFLAGS = -Wall -g
myapp: main.o counter.o
$(CC) $(CFLAGS) main.o counter.o -o myapp
main.o: main.c counter.h
$(CC) $(CFLAGS) -c main.c -o main.o
counter.o: counter.c counter.h
$(CC) $(CFLAGS) -c counter.c -o counter.o
clean:
rm -f *.o myapp
Uso:
make
make clean
Formato de regra do Makefile:
alvo: dependencias
comando
- Alvo: o arquivo a ser gerado
- Dependências: arquivos dos quais o alvo depende — se alguma dependência é mais nova que o alvo, o comando é re-executado
- Comando: como gerar o alvo a partir das dependências
Prevenindo Inclusão Duplicada
Um arquivo de cabeçalho pode ser incluído por múltiplos arquivos fonte, e o mesmo arquivo fonte pode incluí-lo indiretamente várias vezes. Sem proteção, declarações duplicadas causam erros.
Abordagem 1: Include Guard
#ifndef MYHEADER_H
#define MYHEADER_H
void my_func(void);
#endif
Na primeira inclusão, MYHEADER_H está indefinido, então o conteúdo é processado e a macro é definida. Nas inclusões subsequentes, #ifndef avalia como falso e todo o conteúdo é ignorado.
Abordagem 2: #pragma once
#pragma once
void my_func(void);
Conciso e suportado pela maioria dos compiladores, mas não faz parte do padrão — teoricamente menos portável que include guards.
#pragma once é mais conciso; include guards são mais padronizados. Escolha um e use consistentemente.
Princípios de Organização de Arquivos de Cabeçalho
- Cada arquivo
.cdeve ter um arquivo.hcorrespondente com o mesmo nome - Arquivos
.hdevem conter apenas declarações, não definições - A primeira linha de um arquivo
.cdeve incluir seu próprio.h(para verificar que as declarações correspondem às definições) - Cabeçalhos devem ter proteção contra inclusão duplicada
- Declarações
externde variáveis globais vão em.h; definições vão em um arquivo.cespecífico
❓ Perguntas Frequentes
P: Posso colocar definições de variáveis em um arquivo de cabeçalho? R: Não. Se o cabeçalho for incluído por múltiplos arquivos .c, causa definições duplicadas. Definições de variáveis pertencem aos arquivos .c; use declarações extern nos arquivos .h.
P: Qual a diferença entre extern e incluir um cabeçalho diretamente? R: #include insere o conteúdo inteiro do arquivo de cabeçalho; extern declara explicitamente que uma variável ou função está definida em outro lugar. Cabeçalhos tipicamente contêm declarações extern — os dois trabalham juntos.
P: A indentação do Makefile realmente exige Tab? R: Sim, este é um requisito obrigatório do Make. Usar espaços causará erros. Configure seu editor para usar indentação com Tab em Makefiles.
P: Devo escolher #pragma once ou include guards? R: Escolha um e use consistentemente no seu projeto. #pragma once é mais simples mas não é padrão; include guards são padrão mas um pouco mais verbosos. Projetos open-source grandes tendem a usar include guards.
📖 Resumo
- Arquivos de cabeçalho (.h) contêm declarações, arquivos fonte (.c) contêm definições — essa separação é a base da modularização
externdeclara variáveis globais e funções entre arquivos, dizendo ao compilador "definido em outro lugar"- Compilação multi-arquivo deve usar "compilar individualmente com -c, depois ligar" — recompile apenas arquivos modificados
- Makefile automatiza dependências de compilação, evitando a necessidade de digitar comandos longos manualmente
- Previna inclusão duplicada com include guards ou
#pragma once
📝 Exercícios
- Crie um projeto de 3 arquivos:
string_utils.h,string_utils.cemain.c. Implemente uma função de inversão de string e uma função de contagem de caracteres, depois chame e teste-as em main - Escreva um Makefile para o exercício 1 que suporte
makepara compilar emake cleanpara limpar - Omita intencionalmente a proteção contra inclusão duplicada em um arquivo de cabeçalho, depois faça
#includedo mesmo cabeçalho duas vezes em main.c e observe o erro de compilação



