Aula 19 — Protótipos, Header Files, static e inline

Como organizar e proteger funções em C — a base da programação modular

1. Protótipos de Função

Até agora você provavelmente colocou todas as funções antes de main() para que o compilador as conhecesse. Mas e quando o programa cresce e as funções precisam se chamar mutuamente? A solução é o protótipo.

Um protótipo (ou declaração antecipada) é uma linha que informa ao compilador o nome, o tipo de retorno e os tipos dos parâmetros de uma função, antes de sua definição completa aparecer no código. O compilador usa essa informação para verificar chamadas durante a compilação.

Sintaxe

// Protótipo — apenas a assinatura, terminada com ;
float calcularMedia(float a, float b);

// Os nomes dos parâmetros são opcionais no protótipo:
float calcularMedia(float, float);   // equivalente

Exemplo prático: protótipos permitem chamar funções definidas depois

#include <stdio.h>

/* ── Protótipos ───────────────────────────────── */
float calcularMedia(float a, float b);
void classificarAluno(float media);

/* ── main ──────────────────────────────────────── */
int main() {
    float n1 = 7.5, n2 = 8.0;
    float media = calcularMedia(n1, n2);
    classificarAluno(media);
    return 0;
}

/* ── Definições (podem vir depois de main) ─────── */
float calcularMedia(float a, float b) {
    return (a + b) / 2.0;
}

void classificarAluno(float media) {
    if (media >= 7.0)
        printf("Aprovado (%.1f)\n", media);
    else
        printf("Reprovado (%.1f)\n", media);
}
Aprovado (7.8)
Por que usar protótipos? Eles eliminam a obrigação de escrever funções em ordem de uso, e são obrigatórios quando o programa é dividido em múltiplos arquivos — que é exatamente o tema da próxima aula.

2. Arquivos de Cabeçalho (.h)

Você já usa arquivos .h todo dia: #include <stdio.h> inclui o cabeçalho da biblioteca padrão de I/O. Agora você vai criar os seus próprios.

Um arquivo de cabeçalho (header file, extensão .h) é um arquivo de texto que contém protótipos de funções, definições de constantes (#define), definições de tipos (typedef, struct) e qualquer outra coisa que precisa ser compartilhada entre arquivos .c. É a interface pública do seu módulo.

Estrutura de um arquivo .h bem feito

/* matematica.h — cabeçalho do módulo de matemática */

/* ── Inclusion guard: evita que o arquivo seja incluído duas vezes ── */
#ifndef MATEMATICA_H
#define MATEMATICA_H

/* Constantes */
#define PI 3.14159265358979

/* Protótipos das funções públicas */
float calcularMedia(float a, float b);
float areaCirculo(float raio);
int   ehPrimo(int n);

#endif /* MATEMATICA_H */

Inclusion guard — por que é obrigatório

Se dois arquivos .c incluírem o mesmo .h, e um deles incluir o outro, o conteúdo do .h seria inserido duas vezes no mesmo arquivo de compilação, causando erros de "redeclaração". O inclusion guard previne isso:

DiretivaSignificado
#ifndef MATEMATICA_HSe o símbolo MATEMATICA_H ainda não foi definido...
#define MATEMATICA_H...define-o agora (marca que este arquivo já foi lido)
#endifFecha o bloco condicional
Por convenção, o nome do símbolo do inclusion guard é o nome do arquivo em maiúsculas com . trocado por _: o arquivo matematica.h usa MATEMATICA_H. Isso garante unicidade entre arquivos do projeto.

O par .h / .c — interface e implementação

/* ─── matematica.h ─── interface (o que o usuário precisa saber) ─── */
#ifndef MATEMATICA_H
#define MATEMATICA_H
#define PI 3.14159265358979
float calcularMedia(float a, float b);
float areaCirculo(float raio);
int   ehPrimo(int n);
#endif

/* ─── matematica.c ─── implementação (como funciona por dentro) ─── */
#include "matematica.h"   // aspas: arquivo local (não <> de sistema)
#include <math.h>

float calcularMedia(float a, float b) {
    return (a + b) / 2.0f;
}

float areaCirculo(float raio) {
    return PI * raio * raio;
}

int ehPrimo(int n) {
    if (n < 2) return 0;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return 0;
    return 1;
}
#include "arquivo.h" vs #include <arquivo.h>
Aspas "" → o compilador procura primeiro no diretório do projeto (seus próprios headers).
Ângulos <> → o compilador procura apenas nos diretórios padrão do sistema (stdio.h, math.h, etc.).

3. Funções static — Encapsulamento por Arquivo

Por padrão, toda função em C tem ligação externa: qualquer outro arquivo .c pode acessá-la (desde que tenha o protótipo). Às vezes você quer o contrário — uma função que é um detalhe interno, invisível para o resto do programa.

Uma função declarada com static tem ligação interna: ela existe apenas no arquivo onde foi definida. Nenhum outro arquivo pode chamá-la — mesmo que tente incluir um protótipo.
/* ─── validacao.c ─── */
#include <stdio.h>

/* Função PRIVADA — auxiliar interna, não exportada */
static int ehValido(int valor) {
    return valor >= 0 && valor <= 100;
}

/* Função PÚBLICA — visível para outros arquivos */
float calcularMedia(int a, int b) {
    if (!ehValido(a) || !ehValido(b)) {
        printf("Valor fora do intervalo [0,100]!\n");
        return -1.0f;
    }
    return (a + b) / 2.0f;
}
Aspecto Função normal Função static
Visibilidade Todo o programa (ligação externa) Apenas o arquivo onde está definida
Conflito de nomes Dois arquivos não podem ter funções com o mesmo nome Dois arquivos podem ter funções static com o mesmo nome
Aparece no .h? ✅ Deve ter protótipo no .h ❌ Nunca deve aparecer no .h
Analogia em Java Método public Método private
Boas práticas: Sempre declare como static qualquer função auxiliar que não precisa ser conhecida fora do seu arquivo. Isso previne conflitos de nomes em projetos grandes e deixa a API pública mais limpa.

Exemplo: validação privada em um módulo de cadastro

/* ─── cadastro.c ─── */
#include "cadastro.h"
#include <string.h>
#include <stdio.h>

/* Auxiliares estáticas — não aparecem no cadastro.h */
static int nomeValido(const char *nome) {
    return strlen(nome) >= 3 && strlen(nome) <= 50;
}

static int idadeValida(int idade) {
    return idade >= 0 && idade <= 130;
}

/* Função pública */
int cadastrarPessoa(const char *nome, int idade) {
    if (!nomeValido(nome)) {
        printf("Nome inválido: '%s'\n", nome);
        return 0;
    }
    if (!idadeValida(idade)) {
        printf("Idade inválida: %d\n", idade);
        return 0;
    }
    printf("Cadastrado: %s, %d anos\n", nome, idade);
    return 1;
}

4. Funções static inline — Otimização sem Perda de Clareza

Toda chamada de função tem um custo: o programa precisa salvar o estado atual, desviar para o código da função, executar, e retornar. Para funções muito pequenas e chamadas com alta frequência (dentro de loops), esse overhead pode ser significativo.

A palavra-chave inline é uma sugestão ao compilador para que ele substitua a chamada da função pelo seu código diretamente no ponto da chamada — eliminando o overhead da chamada. A combinação static inline é a forma mais segura e recomendada em C.
#include <stdio.h>

/* Função static inline — ideal para operações pequenas e frequentes */
static inline int quadrado(int x) {
    return x * x;
}

static inline float celsius_para_fahrenheit(float c) {
    return c * 9.0f / 5.0f + 32.0f;
}

static inline int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int soma = 0;
    for (int i = 1; i <= 100; i++)
        soma += quadrado(i);   // O compilador pode expandir o código aqui

    printf("Soma dos quadrados de 1 a 100: %d\n", soma);
    printf("37°C = %.1f°F\n", celsius_para_fahrenheit(37.0f));
    printf("max(15, 23) = %d\n", max(15, 23));
    return 0;
}
Soma dos quadrados de 1 a 100: 338350
37°C = 98.6°F
max(15, 23) = 23

Quando usar inline — e quando não usar

✅ Use static inline ❌ Não use inline
Funções com 1 a 5 linhas Funções com loops, switch ou recursão
Operações matemáticas simples Funções raramente chamadas
Funções chamadas dentro de loops críticos Funções que precisam de endereço de memória fixo
Conversões de unidade (ex: graus, bytes) Funções longas (o compilador provavelmente ignorará o hint)
⚠️ inline é apenas uma sugestão: O compilador é livre para ignorá-la. Não use inline com a expectativa de que sempre haverá expansão — use porque o código fica mais claro e o compilador pode aproveitar. A diferença real de performance só importa em loops executados milhões de vezes.

Comparação: #define vs static inline

Muita gente usa #define para mini-funções como #define QUAD(x) ((x)*(x)). Isso funciona, mas tem armadilhas. A versão static inline é mais segura:

/* ❌ Problema com #define — efeito colateral */
#define QUAD(x) ((x)*(x))
int i = 3;
int r = QUAD(i++);   // expande para (i++)*(i++) — incrementa i DUAS vezes!

/* ✅ Correto com static inline */
static inline int quadrado(int x) { return x * x; }
int r2 = quadrado(i++);   // i++ ocorre UMA vez, como esperado

5. Resumo: como as peças se encaixam

Ferramenta Onde fica Para que serve Analogia Java
Protótipo Topo do .c ou no .h Informa assinatura ao compilador antes da definição Declaração de método em interface
Arquivo .h Arquivo separado Interface pública: protótipos, constantes, tipos Arquivo de interface / API pública
Inclusion guard Dentro do .h Evita inclusão dupla Não tem equivalente direto
Função static No .c, sem entrar no .h Função privada ao arquivo Método private
Função static inline No .h ou no .c Eliminação de overhead de chamada para funções pequenas Não tem equivalente direto
Próxima aula: Na Aula 16, você vai colocar tudo isso em prática criando um projeto com múltiplos arquivos no VSCode — compilando e executando main.c + modulo.c + modulo.h juntos.

Quiz — Aula 15

1. Qual é o objetivo principal de um protótipo de função?

2. Por que o inclusion guard (#ifndef / #define / #endif) é necessário?

3. Qual é a diferença entre #include "meuarquivo.h" e #include <stdio.h>?

4. Uma função declarada static em modulo.c pode ser chamada em main.c?

5. O que acontece com o trecho #define DOBRO(x) ((x)+(x)) quando chamado como DOBRO(i++)?

6. Para qual situação static inline é mais adequado?

7. O que pertence ao arquivo .h e o que pertence ao arquivo .c?

8. Uma função static em Java tem um significado diferente do que em C. Em C, static numa função significa: