Cuando el tamaño de la pila es impredecible o se desea evitar límites rigidos, una representación con listas enlazadas ofrece crecimiento bajo demanda. Cada push crea un nuevo nodo y cada pop lo libera, manteniendo la memoria ajustada al uso real.
struct NodeEl nodo se define como un struct con dos campos: el valor almacenado y el puntero al siguiente nodo (el anterior en la pila). En C se acostumbra usar un alias para facilitar su reutilización.
typedef struct Node {
int valor;
struct Node *sig;
} Node;
Podemos reemplazar int por otro tipo o incluso por un void * si se busca generalizar la estructura.
La pila se modela con un puntero a Node que representa el tope. No se requiere un struct adicional, aunque es usual encapsularlo para agrupar funciones relacionadas.
typedef struct {
Node *top;
} Stack;
Con esta definición, stack.top es el punto de entrada hacia todos los nodos. Si es NULL, la pila está vacía.
Inicializar una pila dinámica se reduce a asignar NULL al tope. A partir de allí las funciones push y pop se encargan de modificarlo.
void stack_init(Stack *stack) {
stack->top = NULL;
}
Aunque parezca trivial, establecer el estado inicial evita comportamientos indefinidos y simplifica las comprobaciones de vacío.
Las pilas basadas en nodos ofrecen beneficios claros:
Estas ventajas son valiosas en aplicaciones que procesan flujos desconocidos, como parsers, motores de expresiones o utilidades interactivos.
Trabajar con nodos requiere disciplina en el manejo de punteros:
malloc antes de usar el nodo.top antes de liberar memoria para evitar perder referencias.stack_clear) que libere todos los nodos al final del programa.El siguiente snippet implementa una pila dinámica con operaciones básicas y una función de depuración:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int valor;
struct Node *sig;
} Node;
typedef struct {
Node *top;
} Stack;
void stack_init(Stack *stack) {
stack->top = NULL;
}
int stack_is_empty(const Stack *stack) {
return stack->top == NULL;
}
int stack_push(Stack *stack, int valor) {
Node *nuevo = malloc(sizeof(Node));
if (!nuevo) return 0;
nuevo->valor = valor;
nuevo->sig = stack->top;
stack->top = nuevo;
return 1;
}
int stack_pop(Stack *stack, int *out) {
if (stack_is_empty(stack)) return 0;
Node *tmp = stack->top;
*out = tmp->valor;
stack->top = tmp->sig;
free(tmp);
return 1;
}
int stack_peek(const Stack *stack, int *out) {
if (stack_is_empty(stack)) return 0;
*out = stack->top->valor;
return 1;
}
void stack_print(const Stack *stack) {
printf("Pila dinamica:\n");
for (Node *n = stack->top; n; n = n->sig) {
printf(" %d%s\n", n->valor, n == stack->top ? " <- top" : "");
}
}
void stack_clear(Stack *stack) {
Node *curr = stack->top;
while (curr) {
Node *tmp = curr;
curr = curr->sig;
free(tmp);
}
stack->top = NULL;
}
int main(void) {
Stack stack;
stack_init(&stack);
stack_push(&stack, 5);
stack_push(&stack, 15);
stack_push(&stack, 25);
stack_print(&stack);
int peek;
if (stack_peek(&stack, &peek)) {
printf("Peek: %d\n", peek);
}
int valor;
while (stack_pop(&stack, &valor)) {
printf("Pop: %d\n", valor);
}
stack_clear(&stack);
return 0;
}
Compilar este archivo en CLion produce una salida similar a la versión con arrays, pero con memoria dinámica. Siempre cierra con stack_clear para evitar fugas.