📚 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.
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 |
.h) e implementação (.c), com include guards corretos.# (stringizing).Makefile real, com variáveis (CC, CFLAGS, OBJS), regra genérica %.o: %.c e alvos run e clean.Você vai construir uma biblioteca de estatística para relatórios de sensores, dividida em dois módulos independentes mais o programa principal:
estatistica.h / estatistica.c: funções media(), menor() e maior() sobre um array de double.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.main.c: inclui os dois cabeçalhos, define as leituras, chama a estatistica e passa os resultados para imprimirRelatorio().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)"]
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.
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, omakerespondemissing 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.
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.
🎯 Objetivo: usar o pré-processador com segurança.
⌨️ Faça:
relatorio.h, troque o CLAMP provisório pela versão com todos os parênteses do RF02.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:
CLAMP(x + 1, 0, 100) dá um resultado estranho: faltou parêntese em volta de algum v. Sem eles, a expansão vira algo como x + 1 < 0 ? ..., com a precedência errada."Media" com aspas na saída: você escreveu #define ROTULO(x) "#x". O # já produz a string.🎯 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:
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;
}
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
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");
}
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.
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>.
📋 Projetos do curso de C | 📚 Teoria do Capítulo 17 | 🗂️ Hub de projetos