Dividindo um programa em módulos — .h, .c e main.c compilando juntos
Ao longo das aulas anteriores, escrevemos tudo num único arquivo main.c. Funciona para programas pequenos, mas conforme o projeto cresce isso se torna um problema: o arquivo fica enorme, difícil de navegar, e qualquer alteração numa parte pode quebrar outra.
.h (interface) + .c (implementação), e o main.c orquestra tudo. Os arquivos são compilados independentemente e depois ligados (linked) em um único executável.
| Arquivo | Papel | Contém |
|---|---|---|
aluno.h |
Interface do módulo | Inclusion guard, struct, typedef, protótipos |
aluno.c |
Implementação do módulo | #include "aluno.h" + código das funções |
main.c |
Ponto de entrada | #include "aluno.h" + lógica principal |
Você precisa do VSCode instalado com a extensão C/C++ (da Microsoft) e do compilador GCC disponível no terminal. No Windows, a forma mais simples é instalar o MinGW-w64 via MSYS2 e adicionar o caminho ao PATH do sistema.
Ctrl + `) e digite gcc --version. Se aparecer a versão, está pronto.
meu_projeto/ ├── main.c ├── aluno.h └── aluno.c
Crie a pasta do projeto, abra-a no VSCode com File → Open Folder, e crie os três arquivos clicando no ícone de novo arquivo no painel Explorer.
A forma mais direta é usar o terminal integrado do VSCode (Ctrl + `) e compilar com gcc listando todos os arquivos .c:
# Compilar todos os arquivos .c e gerar o executável "programa" gcc main.c aluno.c -o programa # Executar ./programa # Linux / macOS .\programa.exe # Windows
-Wall: Adicione -Wall ao comando para ativar todos os avisos do compilador — uma boa prática que ajuda a encontrar bugs antes de executar:
gcc -Wall main.c aluno.c -o programa
O botão de play do VSCode, por padrão, compila apenas o arquivo aberto. Para compilar múltiplos arquivos automaticamente, edite o arquivo .vscode/tasks.json:
// .vscode/tasks.json { "version": "2.0.0", "tasks": [ { "label": "Compilar projeto C", "type": "shell", "command": "gcc", "args": [ "-Wall", "${workspaceFolder}/*.c", // compila todos os .c da pasta "-o", "${workspaceFolder}/programa" ], "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"] } ] }
Com isso, Ctrl + Shift + B compila o projeto inteiro. Para executar após compilar, adicione uma segunda task com "dependsOn", ou simplesmente use o terminal.
Vamos construir um mini-sistema de cadastro dividido em três arquivos. O módulo aluno cuida de toda a lógica de dados; o main.c apenas usa a interface.
aluno.h/* ============================================================ aluno.h — Interface do módulo de cadastro de alunos ============================================================ */ #ifndef ALUNO_H #define ALUNO_H /* Constante pública */ #define MAX_ALUNOS 50 #define NOME_MAX 60 /* Tipo público */ typedef struct { int matricula; char nome[NOME_MAX]; float nota1; float nota2; } Aluno; /* Protótipos das funções públicas */ void inicializarCadastro(Aluno lista[], int *total); int cadastrarAluno(Aluno lista[], int *total, int mat, const char *nome, float n1, float n2); void listarAlunos(const Aluno lista[], int total); float mediaGeral(const Aluno lista[], int total); int buscarPorMatricula(const Aluno lista[], int total, int mat); #endif /* ALUNO_H */
aluno.c/* ============================================================ aluno.c — Implementação do módulo de alunos ============================================================ */ #include "aluno.h" #include <stdio.h> #include <string.h> /* ── Funções privadas (static) ──────────────────────────── */ /* Verifica se nota está no intervalo [0, 10] */ static int notaValida(float nota) { return nota >= 0.0f && nota <= 10.0f; } /* Calcula a média de um aluno */ static float mediaAluno(const Aluno *a) { return (a->nota1 + a->nota2) / 2.0f; } /* ── Funções públicas ───────────────────────────────────── */ void inicializarCadastro(Aluno lista[], int *total) { *total = 0; } int cadastrarAluno(Aluno lista[], int *total, int mat, const char *nome, float n1, float n2) { if (*total >= MAX_ALUNOS) { printf("Cadastro cheio!\n"); return 0; } if (!notaValida(n1) || !notaValida(n2)) { printf("Nota invalida para %s.\n", nome); return 0; } lista[*total].matricula = mat; strncpy(lista[*total].nome, nome, NOME_MAX - 1); lista[*total].nota1 = n1; lista[*total].nota2 = n2; (*total)++; return 1; } void listarAlunos(const Aluno lista[], int total) { if (total == 0) { printf("Nenhum aluno cadastrado.\n"); return; } printf("\n%-8s %-30s %6s %6s %7s %s\n", "Matric.", "Nome", "Nota1", "Nota2", "Media", "Situacao"); printf("--------------------------------------------------------------\n"); for (int i = 0; i < total; i++) { float m = mediaAluno(&lista[i]); printf("%-8d %-30s %6.1f %6.1f %7.1f %s\n", lista[i].matricula, lista[i].nome, lista[i].nota1, lista[i].nota2, m, m >= 5.0f ? "Aprovado" : "Reprovado"); } } float mediaGeral(const Aluno lista[], int total) { if (total == 0) return 0.0f; float soma = 0.0f; for (int i = 0; i < total; i++) soma += mediaAluno(&lista[i]); return soma / total; } int buscarPorMatricula(const Aluno lista[], int total, int mat) { for (int i = 0; i < total; i++) if (lista[i].matricula == mat) return i; // retorna índice, ou -1 se não encontrado return -1; }
main.c/* ============================================================ main.c — Ponto de entrada: usa o módulo aluno ============================================================ */ #include <stdio.h> #include "aluno.h" // inclui apenas o .h, não o .c! int main() { Aluno turma[MAX_ALUNOS]; int total; inicializarCadastro(turma, &total); /* Cadastro de alunos de exemplo */ cadastrarAluno(turma, &total, 1001, "Ana Souza", 8.5f, 9.0f); cadastrarAluno(turma, &total, 1002, "Bruno Lima", 4.5f, 5.5f); cadastrarAluno(turma, &total, 1003, "Carla Matos", 7.0f, 7.5f); cadastrarAluno(turma, &total, 1004, "Diego Nunes", 3.0f, 4.0f); /* Listagem */ listarAlunos(turma, total); /* Estatísticas */ printf("\nMedia geral da turma: %.1f\n", mediaGeral(turma, total)); /* Busca */ int pos = buscarPorMatricula(turma, total, 1003); if (pos >= 0) printf("Encontrado: %s (pos %d)\n", turma[pos].nome, pos); else printf("Matricula nao encontrada.\n"); return 0; }
Quando você roda gcc main.c aluno.c -o programa, o compilador faz duas etapas:
| Etapa | O que acontece | Resultado |
|---|---|---|
| 1. Compilação | Cada arquivo .c é compilado separadamente para código-objeto. O compilador usa os protótipos do .h para verificar tipos, mas não precisa do código das funções ainda. |
main.o e aluno.o |
| 2. Ligação (linking) | O linker une todos os arquivos .o e resolve as referências cruzadas — conecta a chamada de listarAlunos() em main.o com a implementação em aluno.o. |
programa (executável) |
make automatizam isso.
# Etapa 1: compilar cada .c para .o sem linkar (-c = compile only) gcc -Wall -c main.c -o main.o gcc -Wall -c aluno.c -o aluno.o # Etapa 2: linkar os .o em um executável gcc main.o aluno.o -o programa
| Erro | Causa | Solução |
|---|---|---|
undefined reference to 'funcao' |
O arquivo .c com a função não foi incluído na compilação |
Adicionar o .c ao comando gcc |
implicit declaration of function |
Função usada sem protótipo visível | Incluir o .h correto no arquivo que chama a função |
multiple definition of 'X' |
Variável ou função definida (não só declarada) diretamente no .h, que é incluído em múltiplos .c |
Mover a definição para o .c; manter apenas declaração no .h |
| Inclusion guard faltando → erros de redeclaração | .h incluído indiretamente mais de uma vez |
Sempre usar #ifndef / #define / #endif no .h |
Usar #include "aluno.c" em vez de "aluno.h" |
Inclui o código-fonte diretamente, duplicando definições | Sempre incluir o .h, nunca o .c |
#include "modulo.c"! Isso insere o código-fonte completo do módulo no arquivo corrente, duplicando todas as definições de função quando ambos são compilados. O correto é incluir apenas o .h e informar o .c ao compilador.
| Aspecto | C | Java |
|---|---|---|
| Unidade de modularização | Par de arquivos .h + .c |
Classe (arquivo .java) |
| Interface pública | Protótipos no .h |
Membros public da classe |
| Implementação privada | Funções static no .c |
Membros private da classe |
| Importar módulo | #include "modulo.h" |
import pacote.Classe; |
| Compilação separada | Manual (ou via Makefile) | Automática pelo compilador Java |
| Risco de inclusão dupla | ✅ Existe — resolvido com inclusion guard | ❌ Não existe — Java gerencia imports |