📚 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.
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 |
struct + typedef e tipos de largura fixa (stdint.h).enum para nomear valores inteiros de forma legível, evitando “números mágicos”.union para reinterpretar a mesma região de memória sob “vestimentas” de tipos diferentes.sizeof(struct) pode ser maior que a soma dos campos, por causa do padding de alinhamento imposto pelo compilador.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:
typedef struct { uint8_t tipo; uint16_t tamanho; uint32_t checksum; } PacoteHeader;.enum TipoPacote { PACOTE_DADOS, PACOTE_ACK, PACOTE_ERRO }; para dar nomes legíveis ao campo tipo.PacoteHeader de exemplo, mostrando o tipo pelo nome (via enum), não só pelo número.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).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.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
O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.
🎯 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!
====================================================
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:
enum TipoPacote { PACOTE_DADOS = 0, PACOTE_ACK = 1, PACOTE_ERRO = 2 };typedef struct { uint8_t tipo; uint16_t tamanho; uint32_t checksum; } PacoteHeader;nome_tipo_pacote(enum TipoPacote tipo) com um switch que devolve "DADOS", "ACK", "ERRO" ou "DESCONHECIDO".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.
🎯 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.
🎯 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.
🎯 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:
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.
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.
📋 Projetos do curso de C | 📚 Teoria do Capítulo 16 | 🗂️ Hub de projetos