Structs permitem agrupar dados relacionados, e ponteiros oferecem flexibilidade na manipulação de memória. Juntos, eles formam a base para estruturas de dados dinâmicas e passagem eficiente de argumentos. Nesta aula, vamos explorar como usar ponteiros para structs, alocar structs na heap e passá-las para funções, evitando cópias desnecessárias e permitindo modificações in-place.

Dominar esse tópico é essencial para implementar listas encadeadas, árvores e outros tipos de dados dinâmicos em C.

Operador ->

Quando você tem um ponteiro para uma struct, o operador ponto (.) não pode ser usado diretamente. Em vez disso, você precisa primeiro desreferenciar o ponteiro com * e depois usar o ponto, ou usar o operador seta (->), que combina as duas operações. A expressão ptr->membro é equivalente a (*ptr).membro.

Exemplo:

#include <stdio.h>

struct Ponto {
    int x;
    int y;
};

int main() {
    struct Ponto p = {10, 20};
    struct Ponto *ptr = &p;

    // Usando operador seta
    printf("x = %d, y = %d\n", ptr->x, ptr->y);

    // Equivalente com desreferência
    printf("x = %d, y = %d\n", (*ptr).x, (*ptr).y);

    return 0;
}

O operador seta é mais legível e amplamente utilizado. Lembre-se: se você tem um ponteiro para struct, use -> para acessar membros; se tem a struct diretamente, use ponto.

Structs na heap

Alocar structs na heap (memória dinâmica) com malloc permite que a struct persista além do escopo da função que a criou, além de possibilitar a criação de estruturas de tamanho variável (como vetores ou listas). Para alocar uma struct na heap, use malloc com o tamanho da struct e depois atribua o ponteiro retornado a um ponteiro do tipo da struct.

Exemplo:

#include <stdio.h>
#include <stdlib.h>

struct Pessoa {
    char nome[50];
    int idade;
};

int main() {
    struct Pessoa *p = (struct Pessoa *) malloc(sizeof(struct Pessoa));
    if (p == NULL) {
        printf("Erro de alocação\n");
        return 1;
    }

    // Acessando membros via seta
    p->idade = 25;
    snprintf(p->nome, sizeof(p->nome), "Maria");

    printf("%s tem %d anos\n", p->nome, p->idade);

    free(p); // Libera a memória alocada
    return 0;
}

É crucial verificar se malloc retornou NULL (falha de alocação) e sempre liberar a memória com free quando não for mais necessária. Alocar structs na heap é comum ao criar nós de listas encadeadas, árvores, etc.

Passando structs para funções

Passar uma struct por valor para uma função faz uma cópia completa da struct, o que pode ser ineficiente para structs grandes. Além disso, alterações feitas na struct dentro da função não afetam a original. Para evitar isso, passe um ponteiro para a struct. Isso permite modificar a struct original e economiza memória e tempo de cópia.

Exemplo de passagem por valor vs. por ponteiro:

#include <stdio.h>

struct Ponto {
    int x;
    int y;
};

// Passagem por valor (cópia)
void move_por_valor(struct Ponto p, int dx, int dy) {
    p.x += dx;
    p.y += dy;
}

// Passagem por ponteiro (referência)
void move_por_ponteiro(struct Ponto *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

int main() {
    struct Ponto ponto = {0, 0};

    move_por_valor(ponto, 5, 3);
    printf("Após valor: (%d, %d)\n", ponto.x, ponto.y); // (0,0) - não alterou

    move_por_ponteiro(&ponto, 5, 3);
    printf("Após ponteiro: (%d, %d)\n", ponto.x, ponto.y); // (5,3) - alterou

    return 0;
}

Use const no ponteiro se a função não precisar modificar a struct, para garantir segurança:

void imprime_ponto(const struct Ponto *p) {
    printf("(%d, %d)\n", p->x, p->y);
}

Exemplos

Vamos consolidar com um exemplo mais completo: uma função que cria e retorna um ponteiro para uma struct alocada na heap, e outra que a libera.

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

struct Aluno {
    char nome[100];
    int matricula;
    float nota;
};

struct Aluno* criar_aluno(const char *nome, int matricula, float nota) {
    struct Aluno *a = (struct Aluno*) malloc(sizeof(struct Aluno));
    if (a != NULL) {
        strncpy(a->nome, nome, sizeof(a->nome) - 1);
        a->nome[sizeof(a->nome) - 1] = '\0';
        a->matricula = matricula;
        a->nota = nota;
    }
    return a;
}

void liberar_aluno(struct Aluno **a) {
    if (*a != NULL) {
        free(*a);
        *a = NULL;
    }
}

int main() {
    struct Aluno *aluno = criar_aluno("João", 12345, 9.5);
    if (aluno != NULL) {
        printf("Aluno: %s, Mat: %d, Nota: %.2f\n",
               aluno->nome, aluno->matricula, aluno->nota);
        liberar_aluno(&aluno);
    }
    return 0;
}

Note que liberar_aluno recebe um ponteiro para ponteiro para poder modificar o ponteiro original para NULL, evitando ponteiros soltos. Esse padrão é comum em bibliotecas que gerenciam memória dinâmica.

Boas práticas

  • Sempre verifique o retorno de malloc e trate erros.
  • Libere memória alocada dinamicamente com free quando não for mais usada.
  • Evite vazamentos de memória: para cada malloc, deve haver um free correspondente.
  • Use const em parâmetros de funções que não modificam a struct.
  • Prefira passar structs grandes por ponteiro para evitar cópias desnecessárias.

Referências

Exercícios

  1. Crie uma struct Retangulo com membros largura e altura (float). Escreva uma função que receba um ponteiro para a struct e calcule a área (largura * altura), retornando o valor. Teste no main.
  2. ✓ Resposta:
    #include <stdio.h>
    
    struct Retangulo {
        float largura;
        float altura;
    };
    
    float area(const struct Retangulo *r) {
        return r->largura * r->altura;
    }
    
    int main() {
        struct Retangulo ret = {5.0, 3.0};
        printf("Área: %.2f\n", area(&ret));
        return 0;
    }
  3. Escreva um programa que aloque dinamicamente um vetor de 3 structs Ponto (com x e y inteiros) usando malloc. Preencha os valores e imprima todos. Não esqueça de liberar a memória.
  4. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    struct Ponto {
        int x;
        int y;
    };
    
    int main() {
        struct Ponto *vetor = (struct Ponto*) malloc(3 * sizeof(struct Ponto));
        if (vetor == NULL) return 1;
    
        for (int i = 0; i < 3; i++) {
            vetor[i].x = i * 10;
            vetor[i].y = i * 10 + 5;
        }
    
        for (int i = 0; i < 3; i++) {
            printf("Ponto %d: (%d, %d)\n", i, vetor[i].x, vetor[i].y);
        }
    
        free(vetor);
        return 0;
    }
  5. Crie uma função que receba um ponteiro para uma struct Pessoa (com nome[50] e idade) e modifique a idade para 30. Teste com uma struct alocada na stack.
  6. ✓ Resposta:
    #include <stdio.h>
    
    struct Pessoa {
        char nome[50];
        int idade;
    };
    
    void definir_idade(struct Pessoa *p, int nova_idade) {
        p->idade = nova_idade;
    }
    
    int main() {
        struct Pessoa p1 = {"Ana", 25};
        definir_idade(&p1, 30);
        printf("%s tem %d anos\n", p1.nome, p1.idade);
        return 0;
    }
  7. Implemente uma função que receba um ponteiro para uma struct Produto (com nome[30], preço float e quantidade int) e aplique um desconto de 10% no preço. A função deve modificar a struct original. Teste com alocação dinâmica.
  8. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct Produto {
        char nome[30];
        float preco;
        int quantidade;
    };
    
    void aplicar_desconto(struct Produto *p) {
        p->preco *= 0.9;
    }
    
    int main() {
        struct Produto *prod = (struct Produto*) malloc(sizeof(struct Produto));
        if (prod == NULL) return 1;
        strcpy(prod->nome, "Caneta");
        prod->preco = 2.50;
        prod->quantidade = 10;
    
        aplicar_desconto(prod);
        printf("%s: R$ %.2f (qtd: %d)\n", prod->nome, prod->preco, prod->quantidade);
        free(prod);
        return 0;
    }
  9. Crie uma struct Livro com título (char[100]), autor (char[50]) e ano (int). Escreva uma função que receba um ponteiro para a struct e imprima os dados formatados. No main, aloque um livro na heap, preencha e chame a função. Depois libere.
  10. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct Livro {
        char titulo[100];
        char autor[50];
        int ano;
    };
    
    void imprimir_livro(const struct Livro *l) {
        printf("\"%s\" por %s (%d)\n", l->titulo, l->autor, l->ano);
    }
    
    int main() {
        struct Livro *livro = (struct Livro*) malloc(sizeof(struct Livro));
        if (livro == NULL) return 1;
        strncpy(livro->titulo, "1984", sizeof(livro->titulo)-1);
        livro->titulo[sizeof(livro->titulo)-1] = '\0';
        strncpy(livro->autor, "George Orwell", sizeof(livro->autor)-1);
        livro->autor[sizeof(livro->autor)-1] = '\0';
        livro->ano = 1949;
    
        imprimir_livro(livro);
        free(livro);
        return 0;
    }