📚 Capítulo do curso: 16 — Structs, Struct Padding, Unions e Enumerações da Especialização em Sistemas com C. Leia o capítulo antes de começar.

📦 Miniprojeto C 16 — Decodificador de Pacotes com Structs, Unions e Bit-Fields

Inspeção de telemetria binária com otimização de Struct Padding e registradores.

⏱️ Tempo estimado 📦 Você vai entregar ✅ Pronto quando
55 min main.c cabeçalho decodificado pelo nome, padding medido e os 4 bytes da union impressos

Diagrama do 📦 Miniprojeto C 16 — Decodificador de Pacotes com Structs, Unions e Bit-Fields

🎯 Objetivos

📋 O problema

Protocolos de rede e formatos de arquivo binário costumam começar com um cabeçalho de tamanho fixo que descreve o que vem a seguir. Você foi encarregado de escrever o decodificador desse cabeçalho para um protocolo simplificado interno.

Seu programa (main.c) deve:

  1. Definir typedef struct { uint8_t tipo; uint16_t tamanho; uint32_t checksum; } PacoteHeader;.
  2. Definir enum TipoPacote { PACOTE_DADOS, PACOTE_ACK, PACOTE_ERRO }; para dar nomes legíveis ao campo tipo.
  3. Preencher e imprimir um PacoteHeader de exemplo, mostrando o tipo pelo nome (via enum), não só pelo número.
  4. Imprimir sizeof(PacoteHeader) e comparar com a soma manual sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t), comentando por que os dois valores podem divergir (padding de alinhamento).
  5. Definir uma union que reinterprete um uint32_t como 4 bytes individuais (uint8_t bytes[4]), e imprimir cada byte separadamente — discutindo que a ordem depende do endianness da máquina.
  6. Bônus temático: usar um struct com bit-fields para representar flags de controle compactas, que somam 8 bits (urgente : 1, confirmado : 1).
ID Requisito
RF01 O programa deve compilar sem nenhum aviso com gcc -Wall -Wextra -Wpedantic -std=c17.
RF02 Deve declarar PacoteHeader via typedef struct com os 3 campos (tipo uint8_t, tamanho uint16_t, checksum uint32_t), incluindo <stdint.h>.
RF03 Deve declarar enum TipoPacote com pelo menos PACOTE_DADOS, PACOTE_ACK, PACOTE_ERRO, e usá-lo para nomear o campo tipo na saída.
RF04 Deve imprimir sizeof(PacoteHeader) e a soma manual dos tamanhos individuais dos campos, com um comentário explicando a possível diferença (padding).
RF05 Deve declarar uma union que sobreponha um uint32_t a um array uint8_t bytes[4], e imprimir os 4 bytes individualmente em hexadecimal.
RF06 Deve mencionar, em printf ou comentário, que a ordem dos bytes observada depende do endianness da máquina (little-endian vs big-endian).
graph LR
    A["PacoteHeader\n(struct + typedef)"] --> B["enum TipoPacote\nnomeia o campo tipo"]
    B --> C["sizeof(PacoteHeader)\nvs soma dos campos"]
    C --> D["union ConversorBytes\ndecompoe uint32_t em bytes"]
    D --> E["struct PacoteFlags\n(bit-fields, 8 bits)"]

    style A fill:#0284c7,stroke:#38bdf8,color:#ffffff
    style D fill:#7c3aed,stroke:#a78bfa,color:#ffffff
    style E fill:#059669,stroke:#34d399,color:#ffffff

🧰 Ambiente

O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.


Passo 1 — Compilar o esqueleto

🎯 Objetivo: partir de um código que compila e roda, antes de escrever qualquer linha.

⌨️ Faça: crie a pasta c_mini_16, salve o código abaixo como main.c e rode:

gcc -Wall -Wextra -Wpedantic -std=c17 main.c -o app
./app          # Linux/macOS
.\app.exe      # Windows (PowerShell)

📄 src/esqueleto.c

/*
 * Miniprojeto 16: Decodificador de Pacotes com Structs, Unions e Bit-Fields
 * Especializacao em Engenharia de Sistemas em C
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

// TODO 1: declare "enum TipoPacote" com PACOTE_DADOS, PACOTE_ACK, PACOTE_ERRO.

// TODO 2: declare "typedef struct { ... } PacoteHeader;" com os campos
// tipo (uint8_t), tamanho (uint16_t) e checksum (uint32_t).

// TODO 3: declare "union ConversorBytes" com um campo "uint32_t valor"
// e um campo "uint8_t bytes[4]" sobrepostos.

const char *nome_tipo_pacote(/* TODO 4: parametro do tipo enum TipoPacote */) {
    // TODO 5: switch/if que retorna a string correspondente ao enum.
    return "DESCONHECIDO";
}

int main(void) {
    printf("====================================================\n");
    printf(" ⚡ Miniprojeto 16: Decodificador de Pacotes\n");
    printf("====================================================\n");

    // TODO 6: declare uma variavel PacoteHeader e preencha os 3 campos
    // com valores de exemplo (ex: tipo=PACOTE_ACK, tamanho=128,
    // checksum=0xDEADBEEF). Imprima os 3 campos, usando
    // nome_tipo_pacote() para o campo tipo.

    printf("\n--- Tamanho real em memoria (padding) ---\n");
    // TODO 7: imprima sizeof(PacoteHeader) e a soma manual dos 3 tipos.
    // Comente por que podem ser diferentes.

    printf("\n--- Union: decompondo um uint32_t em 4 bytes individuais ---\n");
    // TODO 8: declare uma "union ConversorBytes", atribua valor=0x11223344
    // e imprima cada um dos 4 bytes com um laco for.

    printf("====================================================\n");
    printf(" ✅ Status: Operacional e Validado!\n");
    printf("====================================================\n");

    return EXIT_SUCCESS;
}

✅ Checkpoint: zero avisos do gcc, e a saída é:

🖥️ Saída esperada:

====================================================
 ⚡ Miniprojeto 16: Decodificador de Pacotes
====================================================

--- Tamanho real em memoria (padding) ---

--- Union: decompondo um uint32_t em 4 bytes individuais ---
====================================================
 ✅ Status: Operacional e Validado!
====================================================

Passo 2 — enum, struct e o cabeçalho (TODO 1, 2, 4, 5 e 6)

🎯 Objetivo: modelar um cabeçalho binário com tipos de tamanho exato e nomes legíveis.

⌨️ Faça:

  1. enum TipoPacote { PACOTE_DADOS = 0, PACOTE_ACK = 1, PACOTE_ERRO = 2 };
  2. typedef struct { uint8_t tipo; uint16_t tamanho; uint32_t checksum; } PacoteHeader;
  3. Complete nome_tipo_pacote(enum TipoPacote tipo) com um switch que devolve "DADOS", "ACK", "ERRO" ou "DESCONHECIDO".
  4. No main, preencha um PacoteHeader com PACOTE_ACK, 128 e 0xDEADBEEF e imprima os três campos. O checksum vai com 0x%08X.

✅ Checkpoint: Tipo: 1 (ACK), Tamanho: 128 bytes e Checksum: 0xDEADBEEF.


Passo 3 — Medir o padding (TODO 7)

🎯 Objetivo: ver que a struct ocupa mais que a soma dos campos.

⌨️ Faça: imprima a soma sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t) e o sizeof(PacoteHeader). Depois acrescente o struct PacoteFlags com bit-fields (urgente : 1, confirmado : 1, reservado : 6) e imprima o sizeof dele.

✅ Checkpoint: soma 7, struct 8. O byte extra é padding depois do tipo, para que tamanho comece num endereço par. sizeof(struct PacoteFlags) dá 4: os 3 campos somam 8 bits, mas moram numa unidade unsigned int, o único tipo de bit-field sem sinal que o C17 garante.

🧯 Se der errado: se você declarou os bit-fields como uint8_t, o -Wpedantic avisa type of bit-field ... is a GCC extension. Funciona no gcc, mas não é C padrão.


Passo 4 — Union e endianness (TODO 3 e 8)

🎯 Objetivo: ler a mesma memória como uint32_t e como 4 bytes.

⌨️ Faça: union ConversorBytes { uint32_t valor; uint8_t bytes[4]; };. No main, atribua 0x11223344 a valor e imprima bytes[0] a bytes[3] com 0x%02X, mais uma nota sobre endianness.

✅ Checkpoint: em PCs x86-64 e na maioria dos ARM, que são little-endian, bytes[0] = 0x44 e bytes[3] = 0x11: o byte menos significativo vem primeiro.


Passo final — Conferir com o gabarito

🎯 Objetivo: validar a saída completa e o código de retorno.

✅ Checkpoint: a saída é esta, e o código de retorno é 0 (echo $? no Linux/macOS ou echo $LASTEXITCODE no PowerShell):

🖥️ Saída esperada:

====================================================
 ⚡ Miniprojeto 16: Decodificador de Pacotes
====================================================

--- Cabecalho decodificado ---
Tipo:     1 (ACK)
Tamanho:  128 bytes
Checksum: 0xDEADBEEF

--- Tamanho real em memoria (padding) ---
Soma dos campos individuais = 7 bytes
sizeof(PacoteHeader)        = 8 bytes (tamanho real da struct)
O compilador pode inserir bytes de 'padding' entre/apos os
campos para alinhar cada um ao seu proprio tamanho (ex: o
uint32_t 'checksum' alinhado em multiplos de 4 bytes), o que
acelera o acesso pela CPU as custas de alguns bytes ociosos.

--- Bit-field: 3 flags (8 bits) numa unica unidade ---
sizeof(struct PacoteFlags) = 4 byte(s)
urgente=1 confirmado=0

--- Union: decompondo um uint32_t em 4 bytes individuais ---
valor completo = 0x11223344
bytes[0] = 0x44
bytes[1] = 0x33
bytes[2] = 0x22
bytes[3] = 0x11
(A ordem dos bytes acima depende do endianness da maquina:
 little-endian mostra o byte menos significativo primeiro,
 big-endian mostraria o mais significativo primeiro.)
====================================================
 ✅ Status: Operacional e Validado!
====================================================

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

🔑 Ver gabarito: src/main.c

📄 src/main.c

/*
 * Miniprojeto 16: Decodificador de Pacotes com Structs, Unions e Bit-Fields
 * Especializacao em Engenharia de Sistemas em C
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

/* Enum para nomear de forma legivel o campo "tipo" do cabecalho. */
enum TipoPacote {
    PACOTE_DADOS = 0,
    PACOTE_ACK = 1,
    PACOTE_ERRO = 2
};

/* Cabecalho de pacote de rede simplificado. Usamos tipos de largura fixa
 * (stdint.h) porque em protocolos binarios o tamanho EXATO de cada campo
 * importa: "int" pode ter 2, 4 ou 8 bytes dependendo da plataforma, mas
 * "uint32_t" tem sempre 4 bytes em qualquer maquina. */
typedef struct {
    uint8_t tipo;
    uint16_t tamanho;
    uint32_t checksum;
} PacoteHeader;

/* Bit-field compacto para flags de controle: os 3 campos somam 8 bits e
 * dividem UMA unica unidade de armazenamento, em vez de 3 variaveis int
 * separadas (12 bytes). O C17 so garante bit-fields de _Bool, int e
 * unsigned int, por isso a unidade aqui e um unsigned int: 4 bytes. */
struct PacoteFlags {
    unsigned int urgente    : 1;
    unsigned int confirmado : 1;
    unsigned int reservado  : 6;
};

/* Union para reinterpretar os 4 bytes de um uint32_t individualmente.
 * "valor" e "bytes" ocupam o MESMO endereco de memoria: escrever em um
 * campo altera o que se le no outro. */
union ConversorBytes {
    uint32_t valor;
    uint8_t bytes[4];
};

const char *nome_tipo_pacote(enum TipoPacote tipo) {
    switch (tipo) {
        case PACOTE_DADOS: return "DADOS";
        case PACOTE_ACK:   return "ACK";
        case PACOTE_ERRO:  return "ERRO";
        default:           return "DESCONHECIDO";
    }
}

int main(void) {
    printf("====================================================\n");
    printf(" ⚡ Miniprojeto 16: Decodificador de Pacotes\n");
    printf("====================================================\n");

    PacoteHeader cabecalho;
    cabecalho.tipo = (uint8_t)PACOTE_ACK;
    cabecalho.tamanho = 128;
    cabecalho.checksum = 0xDEADBEEF;

    printf("\n--- Cabecalho decodificado ---\n");
    printf("Tipo:     %u (%s)\n", (unsigned)cabecalho.tipo,
           nome_tipo_pacote((enum TipoPacote)cabecalho.tipo));
    printf("Tamanho:  %u bytes\n", (unsigned)cabecalho.tamanho);
    printf("Checksum: 0x%08X\n", cabecalho.checksum);

    printf("\n--- Tamanho real em memoria (padding) ---\n");
    size_t soma_campos = sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint32_t);
    printf("Soma dos campos individuais = %zu bytes\n", soma_campos);
    printf("sizeof(PacoteHeader)        = %zu bytes (tamanho real da struct)\n",
           sizeof(PacoteHeader));
    printf("O compilador pode inserir bytes de 'padding' entre/apos os\n");
    printf("campos para alinhar cada um ao seu proprio tamanho (ex: o\n");
    printf("uint32_t 'checksum' alinhado em multiplos de 4 bytes), o que\n");
    printf("acelera o acesso pela CPU as custas de alguns bytes ociosos.\n");

    printf("\n--- Bit-field: 3 flags (8 bits) numa unica unidade ---\n");
    struct PacoteFlags flags = {1, 0, 0};
    printf("sizeof(struct PacoteFlags) = %zu byte(s)\n", sizeof(struct PacoteFlags));
    printf("urgente=%u confirmado=%u\n", (unsigned)flags.urgente, (unsigned)flags.confirmado);

    printf("\n--- Union: decompondo um uint32_t em 4 bytes individuais ---\n");
    union ConversorBytes conversor;
    conversor.valor = 0x11223344;
    printf("valor completo = 0x%08X\n", conversor.valor);
    for (int i = 0; i < 4; i++) {
        printf("bytes[%d] = 0x%02X\n", i, (unsigned)conversor.bytes[i]);
    }
    printf("(A ordem dos bytes acima depende do endianness da maquina:\n");
    printf(" little-endian mostra o byte menos significativo primeiro,\n");
    printf(" big-endian mostraria o mais significativo primeiro.)\n");

    printf("====================================================\n");
    printf(" ✅ Status: Operacional e Validado!\n");
    printf("====================================================\n");

    return EXIT_SUCCESS;
}

Por que isso importa: esse mesmo padrão — struct de campos de largura fixa lida byte a byte via union — é a base de como drivers de dispositivo, parsers de protocolo de rede (TCP/IP, USB) e formatos de arquivo (PNG, WAV) decodificam dados binários brutos em C.


🏆 Desafio

Escreva uma função void imprimir_bytes_brutos(const PacoteHeader *pacote) que reinterprete o próprio PacoteHeader (não um uint32_t isolado) como um array de unsigned char usando um union que contenha PacoteHeader header; e unsigned char bytes[sizeof(PacoteHeader)];, imprimindo todos os bytes reais em memória, padding incluso. Compare a contagem de bytes impressos com sizeof(PacoteHeader) para confirmar visualmente onde o padding aparece.

✅ Checkpoint: a union com PacoteHeader imprime 8 bytes: 01, um byte de padding com valor indeterminado (pode ser qualquer coisa, porque ninguém o escreveu), 80 00 (o 128 em little-endian) e EF BE AD DE (o checksum). Inicialize a struct com = {0} e o padding passa a sair 00.


🧭 Navegação

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