C Pilhas e Filas

Muitos programas guardam coisas para usar depois, e a ordem em que elas saem é o que importa. O Ctrl+Z de um editor desfaz primeiro a última coisa que você fez. O caixa do banco chama primeiro quem chegou primeiro. Esses dois jeitos têm nome: pilha e fila. Nesta aula você vai construir os dois em C, primeiro com um array e depois com uma lista ligada, e vai ver o truque do array circular.

Você vai aprender
  • O que é uma pilha (o último a entrar é o primeiro a sair) e uma fila (o primeiro a entrar é o primeiro a sair).
  • As operações da pilha: push (empilhar), pop (desempilhar) e ver o topo.
  • Como fazer uma pilha com array e com lista ligada, e um "desfazer" de verdade.
  • As operações da fila: enfileirar e desenfileirar, com a fila do banco.
  • Como fazer uma fila com lista ligada (ponteiros de início e fim) e com array circular (o resto %).
  • Os erros clássicos: tirar de uma pilha vazia, esquecer o %, esquecer de zerar o fim.

Esta aula usa structs, passar por ponteiro e listas ligadas. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: pratos e o banco

Na cozinha da Ana, os pratos limpos ficam empilhados. Ela sempre põe o prato novo em cima e sempre tira o prato de cima. O prato que está embaixo de todos foi o primeiro a ser guardado e vai ser o último a ser usado. Isso é uma pilha (em inglês, stack).

No banco, as pessoas pegam uma senha e entram na fila. Quem chega vai para o fim; o caixa chama quem está no começo. Quem chegou primeiro é atendido primeiro. Isso é uma fila (em inglês, queue).

PilhaFila
RegraO último que entra é o primeiro que saiO primeiro que entra é o primeiro que sai
Sigla em inglêsLIFO (last in, first out)FIFO (first in, first out)
Por onde entraPelo topoPelo fim
Por onde saiPelo topoPelo começo
Nomes das operaçõespush ("empurrar", empilhar), pop ("estourar", desempilhar)enqueue (enfileirar), dequeue (desenfileirar)
No computadorDesfazer (Ctrl+Z), voltar página do navegador, conferir parênteses, a pilha de chamadas de funçõesFila de impressão, mensagens esperando resposta, atendimento por ordem de chegada

Nenhuma das duas deixa você mexer no meio. Essa é a graça: com só duas operações, não tem como errar a ordem.


1. Pilha com array

A forma mais simples de pilha é um array e um número, topo, que diz quantos itens estão guardados. Os itens moram em itens[0], itens[1]… e o de cima está em itens[topo - 1]:

#include <stdio.h>

#define MAX 5

typedef struct {
    int itens[MAX];
    int topo;
} Pilha;

bool push(Pilha *p, int valor) {
    if (p->topo == MAX) {
        return false;
    }
    p->itens[p->topo] = valor;
    p->topo++;
    return true;
}

bool pop(Pilha *p, int *saida) {
    if (p->topo == 0) {
        return false;
    }
    p->topo--;
    *saida = p->itens[p->topo];
    return true;
}

bool ver_topo(const Pilha *p, int *saida) {
    if (p->topo == 0) {
        return false;
    }
    *saida = p->itens[p->topo - 1];
    return true;
}

int main(void) {
    Pilha pratos = {.topo = 0};
    for (int i = 1; i <= 6; i++) {
        if (push(&pratos, i * 10)) {
            printf("push %d\n", i * 10);
        } else {
            printf("push %d: pilha cheia!\n", i * 10);
        }
    }
    int x;
    if (ver_topo(&pratos, &x)) {
        printf("Topo: %d (com %d itens)\n", x, pratos.topo);
    }
    while (pop(&pratos, &x)) {
        printf("pop %d\n", x);
    }
    printf("Vazia: %s\n", pratos.topo == 0 ? "sim" : "não");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pilha_array.c -o pilha_array
$ ./pilha_array
push 10
push 20
push 30
push 40
push 50
push 60: pilha cheia!
Topo: 50 (com 5 itens)
pop 50
pop 40
pop 30
pop 20
pop 10
Vazia: sim
#define MAX 5
A capacidade da pilha. Com um array, ela é fixa: o sexto push é recusado.
int topo;
Quantos itens estão na pilha. Também é a posição livre onde o próximo item vai entrar.
Pilha pratos = {.topo = 0};
Começa vazia. O .topo = 0 é um inicializador designado C99: dá valor ao campo pelo nome, e os campos não citados (o array) ficam zerados.
bool push(Pilha *p, int valor)
Recebe a pilha por ponteiro, para poder mudá-la, e devolve false se ela estiver cheia. Quem chama confere.
p->itens[p->topo] = valor; p->topo++;
Guarda na posição livre e sobe o topo.
bool pop(Pilha *p, int *saida)
O valor tirado sai pelo ponteiro saida; o retorno diz se deu certo. Assim, "pilha vazia" nunca se confunde com um valor de verdade.
p->topo--; *saida = p->itens[p->topo];
Desce o topo e lê o item que estava lá em cima. O valor continua no array, mas agora está na parte "livre" e vai ser sobrescrito no próximo push.
ver_topo
Olha o item de cima sem tirar. Em inglês é o peek ("espiar") ou top.
while (pop(&pratos, &x))
Desempilha até o pop devolver false. Repare que os números saíram na ordem contrária à que entraram.
Três momentos da pilha: com 10, 20 e 30; depois de push 40; depois de pop 10020130234de cimaantes: topo = 31002013024034de cimapush 40: topo = 410020130234de cimapop (sai o 40): topo = 3
A pilha desenhada de baixo para cima, com os índices do array à esquerda. Um push 40 guarda o 40 em itens[3] (verde) e o topo passa de 3 para 4. Um pop faz o caminho inverso: o topo volta a 3 e o 40 é devolvido. Nada é empurrado nem copiado: só o topo sobe e desce.

Experimente: o programa já empilhou as letras de "pilha", uma por uma. Escreva o laço que desempilha todas e mostra cada letra com putchar(c). Como a pilha devolve na ordem contrária, a palavra vai sair de trás para a frente: ahlip.

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

#define MAX 20

typedef struct {
    char letras[MAX];
    int topo;
} Pilha;

bool push(Pilha *p, char c) {
    if (p->topo == MAX) {
        return false;
    }
    p->letras[p->topo++] = c;
    return true;
}

bool pop(Pilha *p, char *saida) {
    if (p->topo == 0) {
        return false;
    }
    *saida = p->letras[--p->topo];
    return true;
}

int main(void) {
    const char *palavra = "pilha";
    Pilha p = {.topo = 0};
    for (size_t i = 0; i < strlen(palavra); i++) {
        push(&p, palavra[i]);
    }

    // tire as letras da pilha, uma por uma, e mostre cada uma com putchar

    putchar('\n');
    return 0;
}
#include <stdio.h>
#include <string.h>

#define MAX 20

typedef struct {
    char letras[MAX];
    int topo;
} Pilha;

bool push(Pilha *p, char c) {
    if (p->topo == MAX) {
        return false;
    }
    p->letras[p->topo++] = c;
    return true;
}

bool pop(Pilha *p, char *saida) {
    if (p->topo == 0) {
        return false;
    }
    *saida = p->letras[--p->topo];
    return true;
}

int main(void) {
    const char *palavra = "pilha";
    Pilha p = {.topo = 0};
    for (size_t i = 0; i < strlen(palavra); i++) {
        push(&p, palavra[i]);
    }

    char c;
    while (pop(&p, &c)) {
        putchar(c);
    }

    putchar('\n');
    return 0;
}

Repare em p->letras[p->topo++] = c; e p->letras[--p->topo]: são as mesmas duas linhas do pilha_array.c juntadas numa só, com o ++ depois (usa e depois soma) e o -- antes (subtrai e depois usa). Veja ++ e --.


2. Pilha com lista ligada: o desfazer

A pilha com array tem tamanho fixo. Com uma lista ligada, ela cresce enquanto houver memória, e as operações já existem: push é inserir no início e pop é remover o primeiro. O topo é a cabeça da lista.

Vamos fazer o "desfazer" de um editor de texto. Cada ação vai para a pilha; o Ctrl+Z tira a de cima:

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

typedef struct Acao {
    char texto[40];
    struct Acao *abaixo;
} Acao;

bool empilhar(Acao **topo, const char *texto) {
    Acao *a = malloc(sizeof *a);
    if (a == NULL) {
        return false;
    }
    snprintf(a->texto, sizeof a->texto, "%s", texto);
    a->abaixo = *topo;
    *topo = a;
    return true;
}

bool desempilhar(Acao **topo, char *saida, size_t tam) {
    if (*topo == NULL) {
        return false;
    }
    Acao *a = *topo;
    snprintf(saida, tam, "%s", a->texto);
    *topo = a->abaixo;
    free(a);
    return true;
}

int main(void) {
    Acao *historico = NULL;
    const char *feitas[] = {"digitou \"Olá\"", "pôs negrito", "trocou a cor"};
    for (int i = 0; i < 3; i++) {
        if (!empilhar(&historico, feitas[i])) {
            fprintf(stderr, "Sem memória\n");
            return 1;
        }
        printf("Fez: %s\n", feitas[i]);
    }
    char ultima[40];
    for (int i = 0; i < 2; i++) {
        if (desempilhar(&historico, ultima, sizeof ultima)) {
            printf("Ctrl+Z desfez: %s\n", ultima);
        }
    }
    printf("Ainda pode desfazer: %s\n", historico != NULL ? historico->texto : "nada");
    while (desempilhar(&historico, ultima, sizeof ultima)) {
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g desfazer.c -o desfazer
$ ./desfazer
Fez: digitou "Olá"
Fez: pôs negrito
Fez: trocou a cor
Ctrl+Z desfez: trocou a cor
Ctrl+Z desfez: pôs negrito
Ainda pode desfazer: digitou "Olá"
LinhaO que faz
struct Acao *abaixo;O prox da lista, com um nome que lembra a pilha: aponta para a ação de baixo.
Acao **topoAs funções recebem o endereço da variável historico (um ponteiro para ponteiro), para mudar o topo diretamente.
snprintf(a->texto, sizeof a->texto, "%s", texto);Copia o texto sem passar do tamanho do campo (veja snprintf). Mais seguro que strcpy.
a->abaixo = *topo; *topo = a;O push: a ação nova aponta para o topo antigo e vira o topo.
*topo = a->abaixo; free(a);O pop: o de baixo vira o topo, e o nó tirado é liberado (o texto já foi copiado para saida).
while (desempilhar(…)) { }No fim, esvazia o que sobrou: cada desempilhar faz um free. O laço não tem corpo porque o trabalho todo está na condição.
A pilha do desfazer: o topo é a última ação corhistoriconegritoOláNULL
A pilha deitada: a cabeça da lista (historico) é o topo, a ação mais recente. O Ctrl+Z tira "trocou a cor" e o topo passa a ser "pôs negrito". A primeira ação ("digitou Olá") é a última a ser desfeita.

O valgrind confirma que o laço final devolveu tudo (3 ações + o bloco do printf):

$ valgrind --leak-check=full ./desfazer 2>&1 | tail -8
==64513== HEAP SUMMARY:
==64513==     in use at exit: 0 bytes in 0 blocks
==64513==   total heap usage: 4 allocs, 4 frees, 4,240 bytes allocated
==64513==
==64513== All heap blocks were freed -- no leaks are possible
==64513==
==64513== For lists of detected and suppressed errors, rerun with: -s
==64513== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

O 2>&1 | tail -8 junta o relatório do valgrind (que sai no stderr) com a saída normal e mostra só as 8 últimas linhas (veja stdin, stdout e stderr).


3. Fila com lista ligada: o banco

Numa fila, entra-se pelo fim e sai-se pelo começo. Com uma lista ligada, as duas pontas precisam ser rápidas, então a fila guarda dois ponteiros: ini (o próximo a ser atendido) e fim (o último que chegou):

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

typedef struct No {
    int senha;
    struct No *prox;
} No;

typedef struct {
    No *ini;
    No *fim;
} Fila;

bool enfileirar(Fila *f, int senha) {
    No *n = malloc(sizeof *n);
    if (n == NULL) {
        return false;
    }
    n->senha = senha;
    n->prox = NULL;
    if (f->fim == NULL) {
        f->ini = n;
    } else {
        f->fim->prox = n;
    }
    f->fim = n;
    return true;
}

bool desenfileirar(Fila *f, int *senha) {
    if (f->ini == NULL) {
        return false;
    }
    No *n = f->ini;
    *senha = n->senha;
    f->ini = n->prox;
    if (f->ini == NULL) {
        f->fim = NULL;
    }
    free(n);
    return true;
}

int main(void) {
    Fila banco = {NULL, NULL};
    for (int s = 1; s <= 3; s++) {
        if (!enfileirar(&banco, s)) {
            return 1;
        }
        printf("Chegou a senha %d\n", s);
    }
    int s;
    if (desenfileirar(&banco, &s)) {
        printf("Caixa chama: %d\n", s);
    }
    if (enfileirar(&banco, 4)) {
        printf("Chegou a senha 4\n");
    }
    while (desenfileirar(&banco, &s)) {
        printf("Caixa chama: %d\n", s);
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -g fila_lista.c -o fila_lista
$ ./fila_lista
Chegou a senha 1
Chegou a senha 2
Chegou a senha 3
Caixa chama: 1
Chegou a senha 4
Caixa chama: 2
Caixa chama: 3
Caixa chama: 4
$ valgrind -q --leak-check=full ./fila_lista
Chegou a senha 1
Chegou a senha 2
Chegou a senha 3
Caixa chama: 1
Chegou a senha 4
Caixa chama: 2
Caixa chama: 3
Caixa chama: 4

As senhas foram chamadas na ordem em que chegaram, mesmo com a senha 4 chegando no meio do atendimento. O valgrind -q não reclamou de nada: nenhum vazamento.

Fila banco = {NULL, NULL};
Fila vazia: os dois ponteiros em NULL.
if (f->fim == NULL) f->ini = n;
enfileirar numa fila vazia: o nó novo é o começo e o fim ao mesmo tempo.
else f->fim->prox = n;
Senão, o último de agora aponta para o novo. Sem andar pela lista: o fim já está guardado.
f->fim = n;
Nos dois casos, o novo vira o fim.
f->ini = n->prox;
desenfileirar: o segundo da fila passa a ser o primeiro.
if (f->ini == NULL) f->fim = NULL;
Se a fila esvaziou, o fim também precisa voltar a NULL. Sem esta linha, ele fica apontando para o nó que o free vai devolver (veja Quando dá errado).
Fila com as senhas 2, 3 e 4: ini aponta para a 2, fim para a 4 2ini34fimNULL Depois de desenfileirar: a 2 sai, ini passa para a 3 2?3ini4fimNULL Depois de enfileirar 5: a 4 aponta para a 5, e fim passa para a 5 3ini45fimNULL
Em cima, a fila com três senhas. No meio, o caixa chamou a 2: o ini andou uma casa e o nó da 2 (tracejado) foi liberado. Embaixo, chegou a senha 5: ela entrou depois do antigo fim. Cada operação mexe só numa ponta.

4. Fila com array circular

Dá para fazer fila com array, mas tem uma pegadinha. Se o começo da fila andar para a direita a cada atendimento, logo ele chega ao fim do array, mesmo com o array quase vazio. Empurrar todo mundo para a esquerda a cada atendimento resolveria, mas é lento.

O truque é tratar o array como um círculo, como as cadeiras numa mesa redonda: depois da última posição vem a primeira de novo. Quem faz a volta é o resto da divisão, o % (veja Operadores Aritméticos): com 4 posições, (3 + 1) % 4 dá 0.

#include <stdio.h>

#define CAP 4

typedef struct {
    int itens[CAP];
    int ini;
    int qtd;
} Fila;

bool enfileirar(Fila *f, int valor) {
    if (f->qtd == CAP) {
        return false;
    }
    int fim = (f->ini + f->qtd) % CAP;
    f->itens[fim] = valor;
    f->qtd++;
    printf("  entra %d na posição %d\n", valor, fim);
    return true;
}

bool desenfileirar(Fila *f, int *saida) {
    if (f->qtd == 0) {
        return false;
    }
    *saida = f->itens[f->ini];
    printf("  sai %d da posição %d\n", *saida, f->ini);
    f->ini = (f->ini + 1) % CAP;
    f->qtd--;
    return true;
}

int main(void) {
    Fila f = {.ini = 0, .qtd = 0};
    int x;
    for (int s = 1; s <= 4; s++) {
        enfileirar(&f, s);
    }
    if (!enfileirar(&f, 99)) {
        printf("  fila cheia: 99 não entrou\n");
    }
    desenfileirar(&f, &x);
    desenfileirar(&f, &x);
    enfileirar(&f, 5);
    enfileirar(&f, 6);
    while (desenfileirar(&f, &x)) {
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra circular.c -o circular
$ ./circular
  entra 1 na posição 0
  entra 2 na posição 1
  entra 3 na posição 2
  entra 4 na posição 3
  fila cheia: 99 não entrou
  sai 1 da posição 0
  sai 2 da posição 1
  entra 5 na posição 0
  entra 6 na posição 1
  sai 3 da posição 2
  sai 4 da posição 3
  sai 5 da posição 0
  sai 6 da posição 1
int ini; int qtd;
A posição do primeiro da fila e quantos itens existem. Com os dois, dá para calcular tudo.
int fim = (f->ini + f->qtd) % CAP;
A posição livre depois do último. Com ini = 2 e qtd = 2: (2 + 2) % 4 = 0. A fila "dá a volta" e usa a posição 0, que o 1 tinha deixado livre.
f->ini = (f->ini + 1) % CAP;
Depois de tirar o primeiro, o começo anda uma casa, também dando a volta: de 3 vai para 0.
if (f->qtd == CAP)
Cheia. Com qtd guardado à parte, "cheia" e "vazia" nunca se confundem (as duas teriam ini igual ao fim).
Depois de sair o 1 e o 2: ini na posição 2, a próxima vaga é a 0 013243inifim Depois de entrar o 5 e o 6: a fila dá a volta e ocupa as posições 0 e 1 50613243iniordem da fila: 3, 4, 5, 6
Em cima, depois de atender o 1 e o 2: as posições 0 e 1 estão livres (cinza), o ini está na 2 e a próxima vaga (fim) é a 0. Embaixo, o 5 e o 6 entraram nas posições 0 e 1 (verde). Lida a partir do ini, dando a volta, a fila é 3, 4, 5, 6.

Experimente: troque CAP para 3 e veja em que posições o 5 e o 6 entram. Depois tire o % CAP das duas contas e rode de novo (é o erro da seção Quando dá errado).

#include <stdio.h>

#define CAP 4

typedef struct {
    int itens[CAP];
    int ini;
    int qtd;
} Fila;

bool enfileirar(Fila *f, int valor) {
    if (f->qtd == CAP) {
        return false;
    }
    int fim = (f->ini + f->qtd) % CAP;
    f->itens[fim] = valor;
    f->qtd++;
    printf("  entra %d na posição %d\n", valor, fim);
    return true;
}

bool desenfileirar(Fila *f, int *saida) {
    if (f->qtd == 0) {
        return false;
    }
    *saida = f->itens[f->ini];
    printf("  sai %d da posição %d\n", *saida, f->ini);
    f->ini = (f->ini + 1) % CAP;
    f->qtd--;
    return true;
}

int main(void) {
    Fila f = {.ini = 0, .qtd = 0};
    int x;
    for (int s = 1; s <= 4; s++) {
        enfileirar(&f, s);
    }
    if (!enfileirar(&f, 99)) {
        printf("  fila cheia: 99 não entrou\n");
    }
    desenfileirar(&f, &x);
    desenfileirar(&f, &x);
    enfileirar(&f, 5);
    enfileirar(&f, 6);
    while (desenfileirar(&f, &x)) {
    }
    return 0;
}

Formas diferentes

EstruturaCom arrayCom lista ligada
PilhaArray + topo. Simples, rápido, sem malloc; tamanho fixo (ou use um array que cresce).Push = inserir no início; pop = remover o primeiro. Cresce sem limite; um malloc e um free por item.
FilaArray circular: ini, qtd e o %. Rápido, tamanho fixo.Ponteiros ini e fim: entra no fim, sai do começo. Cresce sem limite.

Qual usar

  • Sabe o máximo de itens (uma pilha de até 100 parênteses, uma fila de até 50 pedidos)? Use o array: menos código, nenhum malloc, nada para liberar.
  • Não sabe quantos vão ser (o histórico do desfazer, mensagens chegando)? Use a lista ligada, ou um array que cresce com realloc.
  • Qualquer que seja a escolha, esconda-a atrás das funções (push, pop, enfileirar…). Quem usa a pilha não precisa saber se ela é array ou lista, e você pode trocar depois sem mexer no resto do programa.

Quando dá errado

Tirar de uma pilha vazia (sem conferir)

Um pop que não confere se a pilha está vazia:

#include <stdio.h>

#define MAX 5

typedef struct {
    int itens[MAX];
    int topo;
} Pilha;

int pop(Pilha *p) {
    p->topo--;
    return p->itens[p->topo];
}

int main(void) {
    Pilha pratos = {.topo = 0};
    int x = pop(&pratos);
    printf("Tirei %d; agora topo = %d\n", x, pratos.topo);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pop_vazia.c -o pop_vazia
$ ./pop_vazia
Tirei 0; agora topo = -1

O que significa: nenhum erro, nenhum aviso, e um valor inventado. Com topo em 0, o pop leu itens[-1], uma posição antes do array, que pertence a outra coisa. O 0 foi sorte; poderia ser qualquer número. E o topo ficou em -1: o próximo push vai escrever fora do array. Com o AddressSanitizer, o erro aparece na hora:

$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address pop_vazia.c -o pop_vazia
$ ./pop_vazia
=================================================================
==62258==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x76ef711f001c at pc 0x5e09084ec2f2 bp 0x7ffda4d8a480 sp 0x7ffda4d8a470
READ of size 4 at 0x76ef711f001c thread T0
    #0 0x5e09084ec2f1 in pop /home/ana/c/pop_vazia.c:12
    #1 0x5e09084ec418 in main /home/ana/c/pop_vazia.c:17
«  (… mais linhas: o mapa da memória em volta do erro …)
SUMMARY: AddressSanitizer: stack-buffer-underflow /home/ana/c/pop_vazia.c:12 in pop

Stack-buffer-underflow = "leitura antes do começo de um array na pilha". READ of size 4: leu um int (4 bytes), na linha 12 (return p->itens[p->topo];), chamada pela linha 17. O editor do guia não tem sanitizers e mostraria só o valor errado.

Como consertar: o pop confere a pilha vazia e diz se deu certo, como na seção 1: bool pop(Pilha *p, int *saida). Do mesmo jeito, o push confere a pilha cheia.

Fila circular sem o %: posições que não existem

O mesmo circular.c, com int fim = f->ini + f->qtd; e f->ini = f->ini + 1;, sem o resto:

$ gcc -std=c23 -Wall -Wextra sem_resto.c -o sem_resto
$ ./sem_resto
  entra 1 na posição 0
  entra 2 na posição 1
  entra 3 na posição 2
  entra 4 na posição 3
  fila cheia: 99 não entrou
  sai 1 da posição 0
  sai 2 da posição 1
  entra 5 na posição 4
  entra 6 na posição 8
  sai 4 da posição 5
  sai 44602368 da posição 6
  sai -914074094 da posição 7
  sai 6 da posição 8

O que significa: o array tem as posições 0 a 3. O 5 foi escrito na "posição 4", que fica fora do array: ali mora o campo seguinte da struct, o ini. O ini virou 5, e daí em diante tudo desandou: o 6 foi para a "posição 8", o "4" que saiu nem é a senha 4 (é o qtd, lido como se fosse item), e os números gigantes são lixo da memória (mudam a cada execução). O gcc não avisou nada.

Como consertar: toda conta de posição numa fila circular termina com % CAP. Na dúvida, compile com -fsanitize=address e teste os casos de "dar a volta".

Fila com lista: o fim ficou apontando para um nó liberado

Uma versão de desenfileirar sem as linhas if (f->ini == NULL) f->fim = NULL;. O main enfileira 1, desenfileira (a fila fica vazia) e enfileira 2:

$ gcc -std=c23 -Wall -Wextra -g fim_solto.c -o fim_solto
$ ./fim_solto
Primeiro da fila: ninguém
$ valgrind -q ./fim_solto
==62531== Invalid write of size 8
==62531==    at 0x4001213: enfileirar (fim_solto.c:24)
==62531==    by 0x40012E9: main (fim_solto.c:46)
==62531==  Address 0x4aa6048 is 8 bytes inside a block of size 16 free'd
==62531==    at 0x48538BF: free (vg_replace_malloc.c:990)
==62531==    by 0x4001282: desenfileirar (fim_solto.c:37)
==62531==    by 0x40012D8: main (fim_solto.c:45)
==62531==  Block was alloc'd at
==62531==    at 0x4850858: malloc (vg_replace_malloc.c:447)
==62531==    by 0x40011C5: enfileirar (fim_solto.c:15)
==62531==    by 0x40012C5: main (fim_solto.c:44)
==62531==
Primeiro da fila: ninguém

O que significa: a senha 2 entrou, mas a fila diz "ninguém". Depois que a senha 1 saiu, o fim continuou apontando para o nó dela, já liberado. No enfileirar seguinte, f->fim não era NULL, então a linha 24 (f->fim->prox = n;) escreveu dentro do nó morto (Invalid write = "escrita inválida", num bloco free'd, liberado), e o ini nunca recebeu o nó novo, que também vazou.

Como consertar: quando a fila esvaziar, zere os dois ponteiros. Regra geral: a fila está vazia se e só se ini e fim são NULL juntos.

Ignorar o false do push

Se o programa chama push(&p, x); sem olhar o retorno, o item que não coube some sem ninguém saber. Não há erro nem aviso: o bool foi devolvido e jogado fora.

Como consertar: confira (if (!push(&p, x)) { … }), como o pilha_array.c faz. No C23, marque a função com [[nodiscard]] e o compilador avisa quem esquecer (veja Atributos).


Referência rápida

OperaçãoPilha com arrayPilha com lista
Vazia?p->topo == 0topo == NULL
Pushp->itens[p->topo++] = v; (se não estiver cheia)n->abaixo = topo; topo = n;
Popv = p->itens[--p->topo]; (se não estiver vazia)v = topo->valor; aux = topo; topo = topo->abaixo; free(aux);
Ver o topop->itens[p->topo - 1]topo->valor
OperaçãoFila com array circularFila com lista
Vazia?f->qtd == 0f->ini == NULL
Enfileirarf->itens[(f->ini + f->qtd) % CAP] = v; f->qtd++;fim->prox = n; fim = n; (ou ini = fim = n se vazia)
Desenfileirarv = f->itens[f->ini]; f->ini = (f->ini + 1) % CAP; f->qtd--;ini = ini->prox; + free (e fim = NULL se esvaziou)
Ver o primeirof->itens[f->ini]f->ini->valor

Exercícios

1. A regra da pilha

Complete a sigla: numa pilha, o último a entrar é o primeiro a sair, em inglês last in, first out:

2. Dar a volta

Numa fila circular com CAP 5, ini é 3 e qtd é 4. Em que posição entra o próximo item? (3 + 4) % 5 =

3. Push na pilha de array

Complete o push numa linha só (guarda na posição livre e depois sobe o topo):

p->itens[] = valor;

4. Você empilhou 1, 2 e 3 (nessa ordem) e deu dois pop. O que ficou na pilha?

5. A senha 7 entrou na fila do banco, depois a 8, depois a 9. O caixa chama uma pessoa. Quem?

6. Por que a fila com lista ligada guarda um ponteiro para o fim?

Missão final: conferir os parênteses

Missão: todo compilador confere se cada (, [ e { foi fechado pelo par certo, na ordem certa. O jeito é uma pilha: cada abertura vai para a pilha (push); cada fechamento tira o de cima (pop) e confere se ele é o par certo (a função par diz qual é). Se a pilha estiver vazia num fechamento, ou o par não combinar, devolva false. No fim, a pilha precisa estar vazia (isso o código já faz). Complete o laço de balanceado.

(a[b]{c}) -> ok
(] -> erro
((x) -> erro
{[()]} -> ok
) -> erro
#include <stdio.h>

#define MAX 100

typedef struct {
    char itens[MAX];
    int topo;
} Pilha;

bool push(Pilha *p, char c) {
    if (p->topo == MAX) {
        return false;
    }
    p->itens[p->topo++] = c;
    return true;
}

bool pop(Pilha *p, char *saida) {
    if (p->topo == 0) {
        return false;
    }
    *saida = p->itens[--p->topo];
    return true;
}

// Devolve o par de abertura de um fechamento: ')' -> '(', ']' -> '[', '}' -> '{'
char par(char fecha) {
    switch (fecha) {
        case ')': return '(';
        case ']': return '[';
        default:  return '{';
    }
}

bool balanceado(const char *s) {
    Pilha p = {.topo = 0};
    for (int i = 0; s[i] != '\0'; i++) {
        char c = s[i];
        // COMPLETE: abertura ( [ { -> push
        //           fechamento ) ] } -> pop e confira se combina com par(c)
        (void)c;
    }
    return p.topo == 0;
}

int main(void) {
    const char *testes[] = {"(a[b]{c})", "(]", "((x)", "{[()]}", ")"};
    for (int i = 0; i < 5; i++) {
        printf("%s -> %s\n", testes[i], balanceado(testes[i]) ? "ok" : "erro");
    }
    return 0;
}
#include <stdio.h>

#define MAX 100

typedef struct {
    char itens[MAX];
    int topo;
} Pilha;

bool push(Pilha *p, char c) {
    if (p->topo == MAX) {
        return false;
    }
    p->itens[p->topo++] = c;
    return true;
}

bool pop(Pilha *p, char *saida) {
    if (p->topo == 0) {
        return false;
    }
    *saida = p->itens[--p->topo];
    return true;
}

// Devolve o par de abertura de um fechamento: ')' -> '(', ']' -> '[', '}' -> '{'
char par(char fecha) {
    switch (fecha) {
        case ')': return '(';
        case ']': return '[';
        default:  return '{';
    }
}

bool balanceado(const char *s) {
    Pilha p = {.topo = 0};
    for (int i = 0; s[i] != '\0'; i++) {
        char c = s[i];
        if (c == '(' || c == '[' || c == '{') {
            if (!push(&p, c)) {
                return false;
            }
        } else if (c == ')' || c == ']' || c == '}') {
            char aberto;
            if (!pop(&p, &aberto) || aberto != par(c)) {
                return false;
            }
        }
    }
    return p.topo == 0;
}

int main(void) {
    const char *testes[] = {"(a[b]{c})", "(]", "((x)", "{[()]}", ")"};
    for (int i = 0; i < 5; i++) {
        printf("%s -> %s\n", testes[i], balanceado(testes[i]) ? "ok" : "erro");
    }
    return 0;
}

Repare nos três jeitos de errar: (] fecha com o par errado; ((x) sobra um ( na pilha no fim; ) tenta tirar de uma pilha vazia. O seu gcc faz algo parecido quando diz expected ‘}’ at end of input (veja Lendo Erros do Compilador).


Resumo

  • Pilha: o último que entra é o primeiro que sai (LIFO). Operações: push, pop e ver o topo. Usos: desfazer, voltar, conferir parênteses.
  • Fila: o primeiro que entra é o primeiro que sai (FIFO). Operações: enfileirar (no fim) e desenfileirar (do começo). Usos: atendimento por ordem de chegada, filas de tarefas.
  • Pilha com array: itens[] + topo (quantos itens = a próxima posição livre). Pilha com lista: push = inserir no início, pop = remover o primeiro.
  • Fila com lista: ponteiros ini e fim; quando esvaziar, zere os dois. Fila com array: circular, com ini, qtd e toda conta de posição terminando em % CAP.
  • Sempre confira vazia antes de tirar e cheia antes de pôr; devolva bool e entregue o valor por ponteiro. Na lista, cada item tirado ganha o seu free.