Tópicos Intermediários de C

Para quem já passou do básico: const, enum, typedef, errno, variádica, compilação condicional.

1. Uso correto do const

const é como você diz ao sistema de tipos "prometo que não vou modificar isto". Deixa a intenção clara e pega erros em tempo de compilação.
int sum_array(const int *a, int n) {
    int s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    // a[i] = 0; // erro de compilação — somente leitura
    return s;
}

Onde colocar o const

const int *p → *p é somente leitura, p pode mudar
int * const p → p é fixo, *p é gravável
const int * const p → nenhum dos dois
Truque: leia em volta do * — o que estiver à esquerda descreve o alvo; o que estiver à direita descreve o ponteiro.

Declarações de constantes

const int MAX = 100;      // mais seguro em tipo do que #define
const double PI = 3.14159265358979;
Marque sempre argumentos somente-leitura como const em APIs de biblioteca — é um contrato com quem chama sua função.

2. enum

Agrupa constantes inteiras relacionadas sob nomes significativos. Combina bem com switch e elimina números mágicos.
// Ruim: número mágico
int state = 2;
if (state == 3) { /* o que é 3? */ }

// Bom: enum
enum Color { RED, GREEN, BLUE };      // 0, 1, 2
enum Color c = GREEN;
if (c == RED) { /* o significado fica claro */ }

Valores explícitos

enum HttpStatus {
    HTTP_OK = 200,
    HTTP_NOT_FOUND = 404,
    HTTP_SERVER_ERROR = 500
};

Com switch

enum State { STATE_IDLE, STATE_RUN, STATE_STOP };

void handle(enum State s) {
    switch (s) {
        case STATE_IDLE: printf("idle\n"); break;
        case STATE_RUN:  printf("run\n");  break;
        case STATE_STOP: printf("stop\n"); break;
    }
}
Ajuda do compilador: -Wswitch-enum avisa quando um switch não cobre todos os valores do enum.

3. typedef

Dá um novo nome a um tipo existente. Deixa o código mais limpo e autoexplicativo.
// typedef de struct
typedef struct {
    int x;
    int y;
} Point;

Point p = {3, 4};

// ponteiro de função
typedef int (*CmpFn)(const void *, const void *);
CmpFn cmp;

// arrays também
typedef char Buffer[256];
Buffer name;                  // igual a char name[256]

typedefs padrão comuns

typedeftipo subjacenteuso
size_tunsigned longtamanhos & índices
ssize_tlongtamanhos com sinal
int32_t / uint64_tint / unsigned longlargura explícita
time_tlongtempo UNIX
FILEstruct ...descritor de arquivo
Evite esconder ponteiros atrás de um typedef (typedef struct S *SPtr;) — quem lê não consegue mais dizer quais variáveis são ponteiros.

4. Tratamento de erros com errno

Muitas funções padrão definem a variável global errno quando falham. perror ou strerror transforma esse código numa mensagem legível por humanos.
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void) {
    FILE *fp = fopen("arquivo_inexistente.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        fprintf(stderr, "erro %d: %s\n", errno, strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}

Valores comuns de errno

ConstanteSignificado
ENOENTArquivo ou diretório não encontrado
EACCESPermissão negada
ENOMEMMemória esgotada
EINVALArgumento inválido
EAGAINRecurso temporariamente indisponível
EINTRInterrompido por um sinal
Regras:
① Só verifique errno depois que uma função reportar falha.
② Outras chamadas de biblioteca podem sobrescrever errno, então salve-o numa variável local se você for precisar depois.
③ Para funções como strtol, defina errno = 0; antes da chamada para conseguir distinguir sucesso de overflow.

5. Funções variádicas (va_list)

Como escrever funções como printf que aceitam um número variável de argumentos. Usa <stdarg.h>.
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(ap, int);
    }
    va_end(ap);
    return total;
}

int main(void) {
    printf("%d\n", sum(3, 10, 20, 30));  // 60
    printf("%d\n", sum(5, 1, 2, 3, 4, 5));  // 15
}
Restrições importantes:
① Não existe forma em tempo de execução de saber os tipos ou a quantidade de argumentos variádicos. Quem chama e quem é chamado precisam combinar (usando uma contagem como primeiro argumento, uma sentinela ou uma string de formato como em printf).
② Passe o tipo real para va_arg; incompatibilidade é comportamento indefinido.
char e short são promovidos para int — use va_arg(ap, int), não va_arg(ap, char).

Wrapper típico de logging

void log_info(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    fprintf(stderr, "[INFO] ");
    vfprintf(stderr, fmt, ap);
    fprintf(stderr, "\n");
    va_end(ap);
}

log_info("usuário %s entrou em %d", name, ts);

6. Compilação condicional (#ifdef / #if)

Liga ou desliga código em tempo de build. Essencial para builds multiplataforma e instrumentação opcional de depuração.

#ifdef / #ifndef

#ifdef DEBUG
    printf("x = %d\n", x);
#endif

// compilado com 'gcc -DDEBUG' → printf habilitado
// caso contrário, removido

#if com testes numéricos

#define VERSION 3

#if VERSION >= 2
    // recursos da v2+
#else
    // fallback da v1
#endif

Ramificação por plataforma

#if defined(_WIN32)
    #include <windows.h>
    Sleep(1000);
#elif defined(__APPLE__) || defined(__linux__)
    #include <unistd.h>
    sleep(1);
#else
    #error "plataforma não suportada"
#endif

Macros embutidas

MacroSignificado
__FILE__arquivo atual
__LINE__linha atual
__func__nome da função (C99+)
__DATE__ / __TIME__data/hora do build
__STDC_VERSION__versão do padrão C
// Macro prática de depuração
#define LOG(...) \
    fprintf(stderr, "[%s:%d %s] ", __FILE__, __LINE__, __func__), \
    fprintf(stderr, __VA_ARGS__), \
    fprintf(stderr, "\n")

LOG("x = %d", x);
// saída: [main.c:42 main] x = 10
#pragma once: uma alternativa compacta ao clássico guard de include #ifndef FOO_H / #define / #endif. Amplamente suportado, mas não padronizado.

Quiz de Revisão

Teste seu entendimento desta aula!

Q1. Qual a diferença entre const int *p e int * const p?

O primeiro torna o valor apontado imutável; o segundo torna o próprio ponteiro imutável
Significam a mesma coisa
O primeiro é um erro de compilação

O lado do * em que o const está determina se ele se aplica ao valor apontado ou ao próprio ponteiro.

Q2. Quando os valores de um enum não são atribuídos explicitamente, qual é o valor do primeiro elemento?

0
1
Definido pela implementação / aleatório

Sem valores explícitos, um enum começa em 0 e incrementa de 1 em 1. Você pode atribuir um valor no meio para mudar a sequência a partir daquele ponto.

Q3. Qual header define as macros para lidar com argumentos variádicos?

<stdarg.h>
<variadic.h>
<args.h>

va_list, va_start, va_arg e va_end estão todos definidos em <stdarg.h>. É o mesmo header do qual printf depende.