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:

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

C
#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

C
#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

C
#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;
}
💡 Dica: Arquivos de cabeçalho devem conter apenas declarações (protótipos de função, definições de tipo, macros, declarações extern), não definições (corpos de função, inicializações de variáveis). Definições duplicadas causam erros de ligação.

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)

C
int g_count = 0;

Declaração (em um cabeçalho ou outro arquivo .c)

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:

TEXT
project/
├── counter.h
├── counter.c
└── main.c
▶ Experimente

counter.h:

C
#ifndef COUNTER_H
#define COUNTER_H

extern int g_count;
void increment(void);
int get_count(void);

#endif

counter.c:

C
#include "counter.h"

int g_count = 0;

void increment(void) {
    g_count++;
}

int get_count(void) {
    return g_count;
}

main.c:

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;
}
TEXT
count = 3
g_count = 3
⚠️ Atenção: Não inclua um inicializador em uma declaração 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

BASH
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

BASH
gcc counter.c main.c -o myapp

Simples, mas alterar qualquer arquivo requer recompilar tudo — não adequado para projetos grandes.

💡 Dica: A vantagem da compilação individual: se você alterar 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.

MAKEFILE
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
⚠️ Atenção: A indentação do Makefile deve usar caracteres Tab, não espaços. Este é o erro mais comum para iniciantes.

Uso:

BASH
make
make clean

Formato de regra do Makefile:

MAKEFILE
alvo: dependencias
	comando

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

C
#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

C
#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.

💡 Dica: Ambas as abordagens são usadas na prática. #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

❓ 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

📝 Exercícios

  1. Crie um projeto de 3 arquivos: string_utils.h, string_utils.c e main.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
  2. Escreva um Makefile para o exercício 1 que suporte make para compilar e make clean para limpar
  3. Omita intencionalmente a proteção contra inclusão duplicada em um arquivo de cabeçalho, depois faça #include do mesmo cabeçalho duas vezes em main.c e observe o erro de compilação
100%