📚 Capítulo do curso: 17 — Pré-processador, Multi-arquivos (.h/.c) e Automação com Makefile da Especialização em Sistemas com C. Leia o capítulo antes de começar.

🧱 Miniprojeto C 17 — Biblioteca Modular com Makefile

Uma pequena biblioteca de relatórios estatísticos organizada em módulos .h/.c independentes, compilada e automatizada com Makefile.

⏱️ Tempo estimado 📦 Você vai entregar ✅ Pronto quando
60 min 5 arquivos C + Makefile make run mostra média 39.42, menor 0.00 e maior 100.00, e make clean limpa tudo

Diagrama do 🧱 Miniprojeto C 17 — Biblioteca Modular com Makefile

🎯 Objetivos

📋 O problema

Você vai construir uma biblioteca de estatística para relatórios de sensores, dividida em dois módulos independentes mais o programa principal:

  1. estatistica.h / estatistica.c: funções media(), menor() e maior() sobre um array de double.
  2. relatorio.h / relatorio.c: a função imprimirRelatorio(), que imprime os resultados passando cada valor pela macro segura CLAMP(v, min, max), definida em relatorio.h, para que nenhum valor exibido saia da faixa 0–100.
  3. main.c: inclui os dois cabeçalhos, define as leituras, chama a estatistica e passa os resultados para imprimirRelatorio().
  4. Makefile: compila os três .c em objetos separados e linka tudo num executável app.
ID Requisito
RF01 estatistica.h e relatorio.h têm include guards (#ifndef/#define/#endif).
RF02 relatorio.h define #define CLAMP(v, mn, mx) (((v) < (mn)) ? (mn) : (((v) > (mx)) ? (mx) : (v))), com todos os parênteses de proteção.
RF03 estatistica.c implementa media, menor e maior sobre (const double *valores, int tamanho).
RF04 relatorio.c usa o operador # numa macro de rótulo (#define ROTULO(x) #x).
RF05 O Makefile compila com -Wall -Wextra -Wpedantic -std=c17 e tem os alvos all (padrão), run e clean.
graph TD
    H1["estatistica.h\n(interface)"] -.-> M["main.c"]
    H2["relatorio.h\n(interface + macro CLAMP)"] -.-> M
    C1["estatistica.c"] --> O1["estatistica.o"]
    C2["relatorio.c"] --> O2["relatorio.o"]
    M --> O3["main.o"]
    O1 --> L["Linker (ld)"]
    O2 --> L
    O3 --> L
    L --> APP["app (executável)"]

🧰 Ambiente

gcc 8 ou mais novo e make:

Sistema make
Linux / WSL sudo apt install build-essential (já traz o make)
macOS vem com xcode-select --install
Windows use o WSL, ou o MSYS2 (pacman -S make). O mingw32-make do MinGW também lê o Makefile, mas o clean usa rm, que não existe no cmd.

✅ Checkpoint: make --version mostra a versão.


Passo 1 — Montar o esqueleto e compilar com make

🎯 Objetivo: ter os 6 arquivos no lugar e ver o make compilar cada módulo separadamente.

⌨️ Faça: crie a pasta c_mini_17 com os 6 arquivos abaixo e rode make e depois make run.

📄 esqueleto/estatistica.h

#ifndef ESTATISTICA_H
#define ESTATISTICA_H

double media(const double *valores, int tamanho);
double menor(const double *valores, int tamanho);
double maior(const double *valores, int tamanho);

#endif // ESTATISTICA_H

📄 esqueleto/estatistica.c

#include "estatistica.h"

double media(const double *valores, int tamanho) {
    (void)valores; (void)tamanho; // stub: apague esta linha ao implementar
    // TODO 1: some todos os elementos e divida por tamanho (cuidado com casting p/ double)
    return 0.0;
}

double menor(const double *valores, int tamanho) {
    (void)valores; (void)tamanho; // stub: apague esta linha ao implementar
    // TODO 2: percorra o array guardando o menor valor encontrado
    return 0.0;
}

double maior(const double *valores, int tamanho) {
    (void)valores; (void)tamanho; // stub: apague esta linha ao implementar
    // TODO 3: percorra o array guardando o maior valor encontrado
    return 0.0;
}

📄 esqueleto/relatorio.h

#ifndef RELATORIO_H
#define RELATORIO_H

// TODO 4: defina a macro segura CLAMP(v, mn, mx) com todos os parenteses
#define CLAMP(v, mn, mx) (v)

void imprimirRelatorio(double media, double menor, double maior);

#endif // RELATORIO_H

📄 esqueleto/relatorio.c

#include <stdio.h>
#include "relatorio.h"

// TODO 5: use o operador # (stringizing) nesta macro auxiliar de rotulo
#define ROTULO(x) "TODO"

void imprimirRelatorio(double mediaCalculada, double menorValor, double maiorValor) {
    (void)mediaCalculada; (void)menorValor; (void)maiorValor; // stub: apague esta linha ao implementar
    printf("====================================================\n");
    printf(" 📊 RELATORIO ESTATISTICO DE SENSORES\n");
    printf("====================================================\n");
    // TODO 6: imprima media, menor e maior, todos passados por CLAMP(valor, 0.0, 100.0)
    printf("====================================================\n");
}

📄 esqueleto/main.c

#include "estatistica.h"
#include "relatorio.h"

int main(void) {
    double leituras[] = {23.5, 41.2, -5.0, 19.8, 37.0, 120.0};
    int tamanho = 6;

    // Os modulos ainda sao stubs (devolvem 0.0): o trabalho esta em
    // estatistica.c, relatorio.h e relatorio.c (TODO 1 a 6).
    imprimirRelatorio(media(leituras, tamanho), menor(leituras, tamanho),
                      maior(leituras, tamanho));

    return 0;
}

📄 esqueleto/Makefile

CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -std=c17
TARGET = app
OBJS = main.o estatistica.o relatorio.o

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) $^ -o $@

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Dependencias dos cabecalhos: mudou o .h, recompila quem o inclui
main.o: estatistica.h relatorio.h
estatistica.o: estatistica.h
relatorio.o: relatorio.h

run: $(TARGET)
	./$(TARGET)

.PHONY: all run clean
clean:
	rm -f $(OBJS) $(TARGET) $(TARGET).exe

⚠️ No Makefile, as linhas de comando abaixo de cada alvo começam com TAB, não com espaços. Se o seu editor trocar o TAB por espaços, o make responde missing separator.

✅ Checkpoint: o make mostra três gcc -c (um por .c) e um gcc ... -o app, sem avisos. O make run imprime só a moldura do relatório:

🖥️ Saída esperada:

====================================================
 📊 RELATORIO ESTATISTICO DE SENSORES
====================================================
====================================================

Rode make de novo: aparece make: Nothing to be done for 'all'. Nada mudou, então nada é recompilado.

💡 As linhas (void)...; silenciam o aviso de parâmetro não usado enquanto a função é um stub. Apague cada uma ao implementar a função.


Passo 2 — Módulo estatistica (TODO 1 a 3)

🎯 Objetivo: implementar um módulo sem tocar em nenhum outro arquivo.

⌨️ Faça: em estatistica.c, media soma tudo e divide por tamanho. menor e maior começam em valores[0] e percorrem do índice 1 em diante. Rode make run.

✅ Checkpoint: o make recompila só estatistica.o e relinka o app. Os outros .o não mudaram. O relatório ainda sai vazio, porque imprimirRelatorio é um stub.


Passo 3 — Macros e relatório (TODO 4 a 6)

🎯 Objetivo: usar o pré-processador com segurança.

⌨️ Faça:

  1. Em relatorio.h, troque o CLAMP provisório pela versão com todos os parênteses do RF02.
  2. Em relatorio.c, #define ROTULO(x) #x e imprima Media, Menor e Maior com printf("%s: %.2f\n", ROTULO(Media), CLAMP(mediaCalculada, 0.0, 100.0));.

✅ Checkpoint: Media: 39.42, Menor: 0.00 e Maior: 100.00. O -5.0 e o 120.0 foram “grampeados” na faixa 0–100. Repare que mudar o relatorio.h fez o make recompilar main.o e relatorio.o: é para isso que servem as linhas de dependência dos cabeçalhos no Makefile.

🧯 Se der errado:


Passo 4 — Conferir com o gabarito

🎯 Objetivo: validar o resultado e a limpeza dos artefatos.

⌨️ Faça: make run e depois make clean.

✅ Checkpoint: a saída é esta, e depois do make clean não sobra nenhum .o nem o app:

🖥️ Saída esperada:

====================================================
 📊 RELATORIO ESTATISTICO DE SENSORES
====================================================
Media:  39.42
Menor:  0.00
Maior:  100.00
====================================================

Só abra o gabarito depois de chegar até aqui:

🔑 Ver gabarito: gabarito/estatistica.c

📄 gabarito/estatistica.c

#include "estatistica.h"

double media(const double *valores, int tamanho) {
    double soma = 0.0;
    for (int i = 0; i < tamanho; i++) {
        soma += valores[i];
    }
    return soma / tamanho;
}

double menor(const double *valores, int tamanho) {
    double atual = valores[0];
    for (int i = 1; i < tamanho; i++) {
        if (valores[i] < atual) {
            atual = valores[i];
        }
    }
    return atual;
}

double maior(const double *valores, int tamanho) {
    double atual = valores[0];
    for (int i = 1; i < tamanho; i++) {
        if (valores[i] > atual) {
            atual = valores[i];
        }
    }
    return atual;
}
🔑 Ver gabarito: gabarito/relatorio.h

📄 gabarito/relatorio.h

#ifndef RELATORIO_H
#define RELATORIO_H

// Macro segura: parenteses protegem contra bugs de precedencia com expressoes complexas
#define CLAMP(v, mn, mx) (((v) < (mn)) ? (mn) : (((v) > (mx)) ? (mx) : (v)))

void imprimirRelatorio(double mediaCalculada, double menorValor, double maiorValor);

#endif // RELATORIO_H
🔑 Ver gabarito: gabarito/relatorio.c

📄 gabarito/relatorio.c

#include <stdio.h>
#include "relatorio.h"

// Stringizing (#): transforma o proprio identificador em uma string literal
#define ROTULO(x) #x

void imprimirRelatorio(double mediaCalculada, double menorValor, double maiorValor) {
    printf("====================================================\n");
    printf(" 📊 RELATORIO ESTATISTICO DE SENSORES\n");
    printf("====================================================\n");
    printf("%s:  %.2f\n", ROTULO(Media), CLAMP(mediaCalculada, 0.0, 100.0));
    printf("%s:  %.2f\n", ROTULO(Menor), CLAMP(menorValor, 0.0, 100.0));
    printf("%s:  %.2f\n", ROTULO(Maior), CLAMP(maiorValor, 0.0, 100.0));
    printf("====================================================\n");
}
🔑 Ver gabarito: gabarito/main.c

📄 gabarito/main.c

#include "estatistica.h"
#include "relatorio.h"

int main(void) {
    double leituras[] = {23.5, 41.2, -5.0, 19.8, 37.0, 120.0};
    int tamanho = 6;

    double resultadoMedia = media(leituras, tamanho);
    double resultadoMenor = menor(leituras, tamanho);
    double resultadoMaior = maior(leituras, tamanho);

    imprimirRelatorio(resultadoMedia, resultadoMenor, resultadoMaior);

    return 0;
}

Por que isso importa: cada módulo é compilado sozinho em um .o, e o linker só os junta no fim. Num projeto com centenas de arquivos, mudar um .c recompila um objeto em vez do projeto inteiro. É assim que o kernel Linux, o Git e o PostgreSQL são construídos.


🏆 Desafio

Adicione um terceiro módulo desvio.h/desvio.c com double desvioPadrao(const double *valores, int tamanho, double media). Acrescente desvio.o a OBJS no Makefile e use o novo cálculo no main.c. Você só vai mexer em 3 arquivos (o módulo novo, o main.c e o Makefile), sem tocar em estatistica.c nem em relatorio.c.

✅ Checkpoint: o desvio-padrão populacional das 6 leituras é ≈ 38.97 (amostral: ≈ 42.69). O make compila só desvio.o e main.o e relinka. Lembre de -lm no link se usar sqrt de <math.h>.


🧭 Navegação

📋 Projetos do curso de C | 📚 Teoria do Capítulo 17 | 🗂️ Hub de projetos