Le doy a compilar con el gcc y... cero errores. Cero warnings. Una pequeña victoria.
Ejecuto el binario en la pantallita negra, meto los datos, pulso Enter y... ahí está. El mensaje más frío, cruel y destructivo que te puede soltar un ordenador a estas horas:
Segmentation fault (core dumped)
Silencio en la habitación. Ganas de tirar el teclado por la ventana. Otra noche en vela en la que el compilador me ha engañado haciéndome creer que todo estaba bien. Si has estado picando código en C últimamente, sabes exactamente de qué dolor estoy hablando.
¿Qué es realmente un puntero y por qué duele tanto?
El problema con C es que no te lleva de la mano. Si vienes de trastear con Visual Basic o cosas de más alto nivel, esto es un choque frontal. En C, tú eres el dueño absoluto de la máquina.
Un puntero no es más que una variable que guarda una dirección de memoria. En lugar de guardar un número como un 5, guarda el sitio físico en la memoria RAM donde está ese 5. El problema viene cuando C te dice: "Aquí tienes acceso directo a la memoria. Haz lo que quieras. Si te equivocas y escribes en un sitio reservado por el sistema operativo, yo dejaré que lo intentes y luego te mataré el proceso sin explicaciones".
Ese poder es increíble porque te permite controlar el rendimiento al milímetro, pero es un peligro constante. Un pequeño despiste y estás intentando escribir en el limbo, corrompiendo la arquitectura de la memoria de tu propio programa.
El código maldito
Para que veas a qué me refiero, este es el tipo de estupidez que me ha tenido amargado hoy. Mira este trozo de código:
#include <stdio.h>
int main() {
int *mi_puntero; // Declaramos el puntero, pero... ¡no apunta a nada concreto!
// Intentamos guardar un 42 en la dirección a la que apunta
*mi_puntero = 42;
printf("El valor es: %d\n", *mi_puntero);
return 0;
}
Lo ves, ¿verdad? El compilador de GCC se lo traga sin rechistar. Todo parece correcto sintácticamente. Pero cuando el programa se ejecuta, intenta escribir el 42 en una dirección de memoria basura aleatoria. El sistema operativo se da cuenta de la intromisión y corta por lo sano provocando el infame Segmentation fault.
Mis trucos para sobrevivir
A base de golpes, de preguntar en los canales de programación del IRC y de leer foros en inglés con el diccionario al lado, he ido recopilando algunas tácticas de supervivencia:
- Dibuja la memoria en papel: Suena estúpido, pero funciona. Coge un folio y pinta cajitas. Dibuja flechas que apunten de una caja a otra. Cuando intentas entender listas enlazadas o punteros dobles solo mirando la pantalla, tu cerebro colapsa.
- La técnica del
printfa lo bruto: Como aquí no tenemos herramientas mágicas, mi mejor amigo para depurar es llenar el código deprintf("Llego aquí 1\n");,printf("Llego aquí 2\n");. Así vas acotando en qué línea exacta revienta el programa. ¿Es feo? Sí, pero me funciona. - Inicializa siempre a
NULL: Si declaras un puntero y no sabes a dónde va a apuntar todavía, ponleNULL(int *p = NULL;). Es mucho mejor que el programa rompa porque has usado un puntero nulo (que es obvio de detectar) a que rompa por escribir en una dirección de memoria al azar que a veces funciona y a veces no. - Cuidado con los arrays: Recuerda siempre que en C los índices empiezan en cero. Si tu array tiene 10 posiciones e intentas acceder a
array[10], te estás saliendo de la memoria reservada. Puntero desbocado asegurado.
¿Merece la pena el sufrimiento?
A pesar de los dolores de cabeza, de los Core dumped y de las horas perdidas persiguiendo fantasmas en la RAM, aprender C te cambia la forma de pensar.
Te obliga a entender cómo funciona el ordenador por dentro. Ya no eres un usuario avanzado usando comandos que no comprendes; empiezas a entender cómo el hardware gestiona los datos. Te hace más meticuloso, más ordenado y, sinceramente, mucho mejor programador.
Ahora, si me disculpas, voy a poner unos cuantos printf más en mi código a ver si consigo entregar la práctica antes de que amanezca.