Estruturas — Avançado

Structs avançadas são como decorar uma casa — não se trata apenas de construir a estrutura, mas também de usar o espaço de forma eficiente (alinhamento), economizar materiais (bit-fields) e permitir expansão flexível (arrays flexíveis).

Uso Avançado de struct com Funções

Funções que Retornam uma struct

C permite que funções retornem uma struct diretamente — o compilador cuida da cópia:

C
struct Point make_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

int main(void) {
    struct Point pt = make_point(3, 4);
    printf("(%d, %d)\n", pt.x, pt.y);
    return 0;
}
💡 Dica: Retornar uma struct pequena por valor é fine. Para structs grandes, passe um ponteiro e preencha para evitar custo de cópia.

Preenchendo uma struct Através de um Ponteiro

C
void fill_student(struct Student *s, const char *name, int age, float score) {
    strncpy(s->name, name, 19);
    s->name[19] = '\0';
    s->age = age;
    s->score = score;
}

int main(void) {
    struct Student stu;
    fill_student(&stu, "Zhang", 20, 88.5);
    return 0;
}

Proteção const para Parâmetros struct

Para impedir que uma função modifique uma struct, use const no parâmetro ponteiro:

C
void print_student(const struct Student *s) {
    printf("%s %d %.1f\n", s->name, s->age, s->score);
}
⚠️ Atenção: const struct Student *s significa que você não pode modificar a struct apontada através de s, mas s pode apontar para outra coisa.

typedef para Alias de Tipos

typedef cria um alias para um tipo, reduzindo a necessidade de escrever repetidamente a palavra-chave struct.

Uso Básico

C
typedef struct {
    char name[20];
    int age;
    float score;
} Student;

Student s1 = {"Zhang", 20, 89.5};
Student *ps = &s1;
💡 Dica: Não precisa mais escrever struct Student — basta usar Student diretamente.

typedef com Ponteiros de struct

C
typedef struct Node {
    int data;
    struct Node *next;
} Node, *NodePtr;

Node n1 = {10, NULL};
NodePtr head = &n1;
⚠️ Atenção: Quando uma struct referencia a si mesma, você ainda deve usar struct Node *next porque o alias ainda não está em vigor naquele ponto.

Outros Usos de typedef

C
typedef unsigned char Byte;
typedef int (*Comparator)(const void *, const void *);

Byte flag = 0xFF;
Comparator cmp = my_compare;

Exemplo

C
#include <stdio.h>
#include <string.h>

typedef struct {
    char title[50];
    int pages;
    float price;
} Book;

Book create_book(const char *title, int pages, float price) {
    Book b;
    strncpy(b.title, title, 49);
    b.title[49] = '\0';
    b.pages = pages;
    b.price = price;
    return b;
}

void discount(Book *b, float rate) {
    b->price *= rate;
}

void print_book(const Book *b) {
    printf("<%s> %d paginas R$%.2f\n", b->title, b->pages, b->price);
}

int main(void) {
    Book b1 = create_book("C Programming", 320, 59.0);
    print_book(&b1);
    discount(&b1, 0.8);
    print_book(&b1);
    return 0;
}
▶ Experimente
TEXT
<C Programming> 320 paginas R$59.00
<C Programming> 320 paginas R$47.20

Alinhamento de Memória em structs

Os membros de uma struct não são empacotados de forma contígua na memória — o compilador insere bytes de padding de acordo com regras de alinhamento.

Regras de Alinhamento

  1. O offset de cada membro deve ser um múltiplo do tamanho desse membro
  2. O tamanho total da struct deve ser um múltiplo do tamanho do maior membro
C
struct Align1 {
    char a;
    int b;
    char c;
};

struct Align2 {
    char a;
    char c;
    int b;
};
C
printf("%zu\n", sizeof(struct Align1));
printf("%zu\n", sizeof(struct Align2));
TEXT
12
8

Layout de Align1: a (1 byte) + 3 bytes padding + b (4 bytes) + c (1 byte) + 3 bytes padding = 12.

Layout de Align2: a (1 byte) + c (1 byte) + 2 bytes padding + b (4 bytes) = 8.

💡 Dica: Agrupar membros menores juntos reduz os bytes de padding — uma técnica prática para economizar memória.

#pragma pack

Você pode especificar o limite de alinhamento para comprimir uma struct:

C
#pragma pack(push, 1)
struct Packed {
    char a;
    int b;
    char c;
};
#pragma pack(pop)

printf("%zu\n", sizeof(struct Packed));
TEXT
6
⚠️ Atenção: Alinhamento empacotado reduz a eficiência de acesso e pode até causar erros em algumas plataformas. Use-o apenas em cenários que exigem estritamente alinhamento por byte, como análise de protocolos ou manipulação de formatos de arquivo.

Bit-Fields

Bit-fields alocam espaço para membros bit a bit, economizando memória.

C
struct Flags {
    unsigned int ready : 1;
    unsigned int error : 1;
    unsigned int mode  : 3;
    unsigned int       : 0;
    unsigned int count : 12;
};
C
struct Flags f = {1, 0, 5, 1024};
printf("ready=%u error=%u mode=%u count=%u\n", f.ready, f.error, f.mode, f.count);
printf("Tamanho da struct: %zu\n", sizeof(f));
TEXT
ready=1 error=0 mode=5 count=1024
Tamanho da struct: 8
⚠️ Atenção: O layout de bit-fields depende do compilador e não é portável. Você não pode obter o endereço de um membro bit-field (&f.ready é ilegal).

Membros de Array Flexível

O C99 permite que o último membro de uma struct seja um array de comprimento 0, chamado de Membro de Array Flexível.

C
typedef struct {
    int len;
    int data[];
} IntVec;

Ao usar, aloque espaço extra conforme necessário:

C
int n = 5;
IntVec *v = (IntVec *)malloc(sizeof(IntVec) + sizeof(int) * n);
v->len = n;
for (int i = 0; i < n; i++) {
    v->data[i] = i * 100;
}
for (int i = 0; i < v->len; i++) {
    printf("%d ", v->data[i]);
}
free(v);
TEXT
0 100 200 300 400
💡 Dica: Membros de array flexível são comumente usados para implementar structs de tamanho variável. Comparado com o uso de um ponteiro para memória separada, os dados são contíguos e um único free basta.

⚠️ Atenção: O array flexível deve ser o último membro, e a struct deve ter pelo menos um outro membro. Você não pode usar sizeof para obter o tamanho de um array flexível.

❓ Perguntas Frequentes

P: Qual a diferença entre typedef e #define para alias de tipos? R: typedef é processado pelo compilador, segue regras de escopo e lida com tipos de ponteiro corretamente. #define é uma substituição de texto do pré-processador que pode causar erros sutis. Prefira typedef.

P: Por que o alinhamento de memória existe? R: CPUs leem memória de forma mais eficiente em limites alinhados, e algumas arquiteturas geram falhas em acessos não alinhados. O compilador adiciona padding automaticamente para compatibilidade e desempenho.

P: Pode-se obter o endereço de um bit-field? R: Não. Um membro bit-field pode ser menor que um byte e não tem endereço independente. O operador & não pode ser usado em bit-fields.

P: Array flexível ou membro ponteiro — qual é melhor? R: Arrays flexíveis armazenam dados de forma contígua com uma única alocação e desalocação, e são amigáveis ao cache. Membros ponteiro podem apontar para qualquer lugar, oferecendo mais flexibilidade mas exigindo duas alocações e dois frees. Arrays flexíveis são a escolha mais simples.

📖 Resumo

📝 Exercícios

  1. Defina uma struct contendo char, short, int e double. Tente duas ordenações diferentes de membros e use sizeof para verificar a diferença de tamanho
  2. Use typedef para definir um tipo de nó de lista ligada, depois escreva funções para criar e percorrer uma lista ligada simples
  3. Use um membro de array flexível para implementar uma struct de string dinâmica (com len e data[]), suportando uma operação de adicionar caractere
100%