2 - Conceptos esenciales previos en C

Contexto del lenguaje

Antes de implementar una pila en C conviene alinear nociones de memoria, tipos y abstracciones básicas del lenguaje. Cada decisión sobre el diseño (arreglo fijo, lista dinámica, combinación de ambos) se fundamenta en cómo se reservan y se liberan los recursos.

En este tema recordamos conceptos esenciales para que, en los siguientes, podamos enfocarnos en la lógica de la pila sin dudas sobre el soporte que brinda el lenguaje.

2.1 Variables estáticas vs dinámicas

Las variables estáticas se reservan en tiempo de compilación o en el stack de ejecución y tienen tamaño fijo. Las dinámicas se solicitan en el heap mediante funciones como malloc y requieren llamada explícita a free.

int contador_global = 0;    /* estatico, dura toda la ejecucion */

void ejemplo(void) {
  int buffer_local[8];     /* estatico en el stack, se destruye al salir */

  int *dinamico = malloc(sizeof(int) * 8);
  if (!dinamico) return;   /* validar reserva */

  /* ...usar dinamico... */
  free(dinamico);
}

Una pila basada en arreglos puede usar memoria estática si el límite es conocido; en cambio, si se espera crecimiento imprevisible, conviene administrar nodos en memoria dinámica.

2.2 Arrays y acceso por índice

Un array en C es un bloque contiguo donde cada elemento se accede con un índice enteros desde 0. El compilador no realiza verificaciones de rango, por lo que el programador debe garantizar que los índices sean válidos.

#define CAPACIDAD 5

int numeros[CAPACIDAD] = {0};
for (int i = 0; i < CAPACIDAD; ++i) {
  numeros[i] = i * 10;
}

int tercero = numeros[2]; /* retorna 20 */

Las pilas basadas en arrays dependen de esta aritmética para desplazar el tope. Si se excede el límite, el comportamiento es indefinido, por lo que se debe proteger con condicionales.

2.3 Estructuras (struct)

Los struct agrupan campos heterogéneos bajo un mismo identificador. Son la base para crear TDAs en C y encapsular el estado de la pila.

typedef struct {
  int datos[CAPACIDAD];
  int tope;
} PilaArray;

typedef struct Nodo {
  int valor;
  struct Nodo *sig;
} Nodo;

Al combinar struct con funciones asociadas podemos encapsular los detalles de la implementación: el usuario opera con push, pop o peek sin conocer cuál representación interna se utiliza.

2.4 Punteros y memoria dinámica

Los punteros permiten referenciar direcciones de memoria. En implementaciones dinámicas de pilas se utilizan para enlazar nodos y para liberar correctamente la memoria reservada.

Nodo *crear_nodo(int valor) {
  Nodo *nuevo = malloc(sizeof(Nodo));
  if (!nuevo) return NULL;
  nuevo->valor = valor;
  nuevo->sig = NULL;
  return nuevo;
}

void liberar_pila(Nodo **tope) {
  while (*tope) {
    Nodo *tmp = *tope;
    *tope = (*tope)->sig;
    free(tmp);
  }
}

Los punteros dobles (Nodo **) se emplean para modificar el tope desde funciones auxiliares, permitiendo a la pila actualizase sin usar valores de retorno para el nodo principal.

2.5 ¿Cuándo conviene usar cada representación?

La representación de la pila depende de los requisitos de la aplicación:

  • Arrays estáticos: ideales cuando el tamaño máximo es conocido y pequeño, como en analizadores simples o buffers temporales. Ofrecen cache-friendly y evitan malloc.
  • Arrays dinámicos (realloc): permiten crecer bajo demanda conservando acceso por índice, aunque implican copias cuando se duplica la capacidad.
  • Listas enlazadas: cada push reserva un nodo, lo que elimina el límite físico a costa de mayor fragmentación y del control manual de memoria.

En resumen, se elige la representación que minimice el riesgo en el uso concreto: limitaciones conocidas → arrays; crecimiento impredecible → nodos dinámicos; necesidades mixtas → envoltorios que cambien de estrategia según la ocupación.