Declarar una variable es, esencialmente, decirle al compilador que reserve un espacio de memoria de un tipo específico bajo un nombre concreto. La sintaxis básica sigue el patrón tipo nombre = valor;. Sin embargo, hay una distinción crítica que debes entender desde el primer día: dónde vive esa variable en la memoria.
Cuando declaras una variable dentro de una función (una variable local), esta se almacena en la pila (stack). El problema es que, por razones de rendimiento, el sistema operativo no limpia ese espacio cada vez que llamas a una función; simplemente mueve un puntero. Si no asignas un valor inmediatamente (lo que llamamos inicialización), la variable contendrá lo que sea que hubiera en esa dirección de memoria anteriormente: basura. Intentar leer este valor es comportamiento indefinido (Undefined Behavior), lo que significa que tu programa puede funcionar por pura suerte hoy, pero fallar estrepitosamente mañana con un cambio de optimización en el compilador.
Por el contrario, las variables globales o estáticas se almacenan en segmentos de memoria específicos (como el segmento .bss) que el cargador del sistema operativo garantiza que están inicializados a cero antes de que tu main empiece. Debes inicializar siempre tus variables locales de forma explícita para evitar comportamientos erráticos, a menos que estés seguro de que su valor será sobrescrito inmediatamente (como en un bucle).
Además, debes conocer la evolución de la sintaxis. En el estándar C89, todas las declaraciones debían estar al principio del bloque. A partir de C99, puedes mezclar declaraciones con código ejecutable, lo que permite un diseño más limpio.
#include <stdio.h>
/* Las variables globales se inicializan a cero automáticamente. */
int global_var;
int main(void) {
/* C99 permite declarar variables en mitad del flujo. */
int local_init = 42;
int local_junk; /* ¡Cuidado! Contiene basura de la pila. */
/* Inicialización múltiple en una sola línea. */
int a = 1, b = 2, c = 3;
/* Variable estática: vive en el segmento de datos y se inicializa a cero. */
static int static_var;
/* Inicialización de arrays. */
int arr_partial[5] = {1, 2, 3}; /* Los elementos restantes son cero. */
int arr_full_zero[5] = {0}; /* Técnica estándar para limpiar todo el array. */
/* C99: Inicializadores designados para saltar a índices específicos. */
int arr_des[5] = {[1] = 10, [3] = 40};
/* Imprimimos valores para verificar el comportamiento. */
printf("Global: %d\n", global_var);
printf("Static: %d\n", static_var);
printf("Local init: %d\n", local_init);
printf("a, b, c: %d, %d, %d\n", a, b, c);
printf("Array parcial: ");
for(int i = 0; i < 5; i++) printf("%d ", arr_partial[i]);
printf("\nArray designado: ");
for(int i = 0; i < 5; i++) printf("%d ", arr_des[i]);
printf("\nArray cero: ");
for(int i = 0; i < 5; i++) printf("%d ", arr_full_zero[i]);
printf("\n");
/* Nota: No imprimimos local_junk para evitar comportamiento indefinido. */
return 0;
}
Desglose del ejemplo
En el código anterior, global_var y static_var resultan ser 0 sin que hayamos hecho nada, porque el cargador del sistema operativo se encarga de limpiar los segmentos de datos antes de la ejecución. En cambio, local_junk es una zona de peligro; su valor es impredecible porque simplemente ocupa un hueco en la pila que ya tenía contenido de llamadas de funciones previas.
Fíjate en arr_partial. Aunque solo proporcionamos tres valores {1, 2, 3}, el estándar C exige que todos los elementos restantes del array se rellenen con cero. Esta es la forma más eficiente de asegurar que un array sea seguro: inicializarlo con {0} para limpiar toda la estructura.
Con arr_des, hemos usado la sintaxis de inicializadores designados introducida en C99. Esto es extremadamente útil en estructuras o arrays grandes donde solo necesitas modificar índices específicos (en este caso, el 1 y el 3), dejando el resto en cero. Finalmente, observa que a, b y c demuestran que podemos declarar múltiples variables del mismo tipo en una sola línea, siempre que la sintaxis lo permita.
El compilador es inteligente, pero no infalible. Aunque utilices banderas como -Wuninitialized en gcc, el análisis de flujo del compilador es limitado. Si una variable solo puede tener basura en un camino lógico muy complejo de un if o un switch, es probable que el compilador no te avise, dejando la bomba de tiempo en tu código.
El error frecuente
El error más sutil es la lectura de una variable no inicializada.
int calcular_suma(int x) {
int resultado; // ¡Error! No tiene valor inicial.
return x + resultado;
}
Aunque parezca que resultado es 0, es probable que contenga un valor residual de la pila. Esto es comportamiento indefinido. Un compilador con optimizaciones activadas (-O2 o -O3) podría observar que resultado nunca se inicializa y decidir, por “optimización”, que la función siempre devuelve un valor constante o incluso eliminar la variable por completo, rompiendo la lógica de tu programa de forma impredecible. Herramientas como AddressSanitizer (ASan) o Valgrind son fundamentales para detectar estos errores que no siempre causan un fallo inmediato (crash), sino que corrompen la lógica silenciosamente.
N° 20