Aula 18 — Programação Multi-arquivo no VSCode

Dividindo um programa em módulos — .h, .c e main.c compilando juntos

1. Por que Dividir em Múltiplos Arquivos?

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.

Programação multi-arquivo é o padrão de organizar um projeto C em arquivos separados por responsabilidade: cada módulo tem seu par .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

2. Configurando o VSCode para C Multi-arquivo

Pré-requisitos

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.

Para verificar se o GCC está disponível: Abra o terminal do VSCode (Ctrl + `) e digite gcc --version. Se aparecer a versão, está pronto.

Estrutura de diretório recomendada

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.

Compilando via terminal integrado

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
Flag -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

Configurando o botão ▶ (Run) para projetos multi-arquivo

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.

3. Projeto Completo: Módulo de Cadastro de Alunos

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.

Arquivo 1: 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 */

Arquivo 2: 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;
}

Arquivo 3: 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;
}

Resultado da execução

Matric. Nome Nota1 Nota2 Media Situacao
--------------------------------------------------------------
1001 Ana Souza 8.5 9.0 8.8 Aprovado
1002 Bruno Lima 4.5 5.5 5.0 Aprovado
1003 Carla Matos 7.0 7.5 7.2 Aprovado
1004 Diego Nunes 3.0 4.0 3.5 Reprovado

Media geral da turma: 6.1
Encontrado: Carla Matos (pos 2)

4. Como o Compilador Processa Múltiplos Arquivos

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)
Por que isso importa? Essa separação permite recompilar apenas o arquivo que mudou. Em projetos grandes com dezenas de arquivos, isso economiza minutos de compilação. Ferramentas como make automatizam isso.

Compilação em duas etapas explícitas (opcional, mas instrutivo)

# 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

5. Erros Comuns em Projetos Multi-arquivo

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
⚠️ Nunca faça #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.

6. C vs. Java — Organização em Módulos

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

Quiz — Aula 37

1. Para compilar main.c e aluno.c juntos gerando o executável programa, qual comando está correto?

2. O erro undefined reference to 'listarAlunos' durante a linkagem indica:

3. Por que em main.c escrevemos #include "aluno.h" e não #include "aluno.c"?

4. No tasks.json do VSCode, o que ${workspaceFolder}/*.c faz?

5. Qual é a diferença entre a etapa de compilação e a etapa de linkagem (linking)?

6. A função static int notaValida(float nota) em aluno.c está marcada como static. Isso significa que:

7. Qual vantagem prática a separação em múltiplos arquivos traz em projetos grandes?

8. O que acontece se você remover o inclusion guard de aluno.h e um arquivo incluir "aluno.h" indiretamente duas vezes?