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 extern nã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 extern corretamente).
  • Falta de bibliotecas: quando funções como sqrt exigem a biblioteca matemática (-lm no 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 extern nos cabeçalhos e defina-as em apenas um arquivo .c.
  • Use include guards em todos os cabeçalhos.
  • Compile com -Wall e -Wextra para capturar avisos.

Referências

Exercícios

  1. Crie um módulo calculadora.h e calculadora.c que declare e defina as funções soma, subtrai, multiplica e divide. Escreva um main.c que use essas funções para calcular a expressão (10 + 5) * 2 / 3 e 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;
    }
    
  2. Defina uma variável global int contador em um arquivo contador.c. Em outro arquivo main.c, declare-a com extern e incremente-a em um loop de 1 a 10, imprimindo o valor a cada iteração.

    ✓ Resposta:
    // contador.c
    int contador = 0;
    
    // main.c
    #include 
    
    extern int contador;
    
    int main() {
        for (int i = 0; i < 10; i++) {
            contador++;
            printf("Contador: %d\n", contador);
        }
        return 0;
    }
    
  3. Compile os arquivos do exercício 1 separadamente (gerando arquivos-objeto) e depois linke-os. Escreva os comandos do GCC.
  4. ✓ Resposta:
    gcc -c main.c -o main.o
    gcc -c calculadora.c -o calculadora.o
    gcc main.o calculadora.o -o programa
    
  5. Explique o que é uma referência indefinida e dê um exemplo de código que cause esse erro.
  6. ✓ Resposta: Uma referência indefinida ocorre quando o linker não encontra a definição de um símbolo (função ou variável) que foi referenciado. Por exemplo, se você declara 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.
  7. 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), e main.c que lê 5 números do usuário, armazena em um vetor, calcula a média e imprime o vetor e a média.
  8. ✓ Resposta:
    // 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;
    }