Passe de um único arquivo .c para um layout realista de header/source/Makefile.
.c fica difícil de ler e lento para recompilar. Dividi-lo traz várias vantagens:make descobre isso para você).// declarations only #ifndef MATHUTIL_H #define MATHUTIL_H int add(int a, int b); int mul(int a, int b); int factorial(int n); #endif
#include "mathutil.h" int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); }
#include <stdio.h> #include "mathutil.h" int main(void) { printf("2+3 = %d\n", add(2, 3)); printf("4*5 = %d\n", mul(4, 5)); printf("5! = %d\n", factorial(5)); return 0; }
.h lista o que este módulo exporta (declarações), enquanto o .c contém como ele funciona de fato (implementação). Um leitor deveria conseguir usar o módulo só com o header.#include "..." vs. <...>: use aspas para headers do seu próprio projeto e colchetes angulares para headers de bibliotecas padrão ou externas.main.c mudou, você só recompila ele e re-linka. É exatamente isso que o make automatiza.| Flag | Significado |
|---|---|
-c | Só compila, não linka. Produz um arquivo .o. |
-o nome | Define o nome do arquivo de saída. |
-Wall -Wextra | Habilita os avisos comuns. Sempre use. |
-g | Inclui informações de depuração (para usar com gdb). |
-O0 / -O2 | Sem otimização / otimização padrão para release. |
-std=c11 / -std=c17 | Seleciona a versão do padrão C. |
-I dir | Adiciona um caminho de busca de headers. |
-L dir | Adiciona um caminho de busca de bibliotecas. |
-l nome | Linka libnome — por exemplo, -lm para a biblioteca matemática. |
-D MACRO | Define uma macro do pré-processador. |
.c:// top and bottom of mathutil.h #ifndef MATHUTIL_H #define MATHUTIL_H /* header contents */ #endif
#pragma once — uma única linha que faz o mesmo trabalho. Não está no padrão C, mas é amplamente portátil.extern.c, coloque uma declaração extern no header (só nome e tipo) e a definição real em exatamente um arquivo .c.#ifndef CONFIG_H #define CONFIG_H extern int g_verbose; // declaration only #endif
#include "config.h" int g_verbose = 0; // the one and only definition
int g_verbose; no header: cada .c que incluísse ele teria sua própria definição, gerando um erro de "definição múltipla" no linker.make lê um Makefile, descobre quais arquivos mudaram e reconstrói apenas o necessário.# Makefile — filename must start with capital M # IMPORTANT: lines under a target must start with a TAB character app: main.o mathutil.o gcc main.o mathutil.o -o app main.o: main.c mathutil.h gcc -c main.c mathutil.o: mathutil.c mathutil.h gcc -c mathutil.c clean: rm -f *.o app
CC = gcc
CFLAGS = -Wall -Wextra -O2 -g
OBJS = main.o mathutil.o
TARGET = app
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# implicit rule handles .c → .o automatically
main.o: main.c mathutil.h
mathutil.o: mathutil.c mathutil.h
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: clean
.PHONY: declara alvos que não são arquivos reais. Sem isso, um arquivo chamado clean existindo no disco enganaria o make, fazendo-o achar que o alvo já está atualizado.CFLAGS += -MMD -include $(OBJS:.o=.d)
-MMD do gcc escreve arquivos .d listando dependências de headers, então o make automaticamente reconstrói sempre que qualquer header incluído muda.mathutil.h, mathutil.c, main.c e um Makefile, depois rode make e ./app.mathutil.c que use sqrt() de <math.h>. Adicione -lm em LDFLAGS e confirme que o link funciona.make debug com -g -O0 e make release com -O2.Confira sua compreensão desta aula!
.c de uma vez?Liste vários arquivos .c na linha de comando e o gcc vai compilar cada um e linkar todos juntos no final.
#ifndef / #define / #endifMuitos compiladores também suportam #pragma once, mas para máxima portabilidade o guard clássico com #ifndef ainda é o padrão.
alvo: dependências em uma linha, comandos na próxima linha com TABLinhas de comando precisam começar com um caractere TAB literal — essa é uma regra rígida do make.