Para comprender realmente por qué los punteros se comportan como lo hacen, hay que dejar de pensar en términos de variables abstractas y empezar a visualizar el hardware. Es un modelo simple, pero es la base de todo, desde fallas de segmento hasta vulnerabilidades de seguridad. Si no entiende los conceptos básicos de bits, bytes y palabras, probablemente debería hacer una pausa aquí y repasar eso primero. El resto de esta explicación supone que conoces la diferencia entre un byte y un bit.
Su computadora tiene memoria o RAM (memoria de acceso aleatorio). Es posible que tenga instalados 16, 32 o 64 gigabytes. Esto no es sólo almacenamiento; es el espacio de trabajo. Contiene el código que su CPU está ejecutando actualmente y los datos que esos programas están manipulando en este preciso segundo. Piense en la RAM como una matriz lineal masiva de bytes.
Cada byte de esa matriz tiene una dirección. El primer byte es la dirección 0. El siguiente es el 1. Luego el 2, el 3, y así sucesivamente. Estas direcciones funcionan exactamente como índices en una matriz de programación estándar. Debido a que la CPU puede saltar a cualquier dirección instantáneamente, se llama “memoria de acceso aleatorio”. No es necesario que se lea secuencialmente. Puede agarrar lo que necesite, cuando lo necesite. Cuando el compilador necesita almacenar un tipo de datos mayor, simplemente toma un bloque de bytes contiguos. Un número de coma flotante estándar, por ejemplo, suele ocupar 4 bytes contiguos.
Veamos una declaración global en C:
Para usted, esta es una variable llamada f que contiene un flotador. Para la computadora, este es un comando para reservar 4 bytes específicos en la matriz de memoria. Digamos que el compilador asigna estos bytes a la ubicación de memoria 248.440.
Cuando escribes:
El compilador no piensa en “actualizar la variable f”. Piensa “cargar el valor 3,14 en la dirección de memoria 248.440”. La abstracción es delgada. En el fondo, se trata de direcciones y los valores que se encuentran en esas direcciones.
Esta naturaleza mecánica de la memoria produce algunos efectos secundarios desagradables. Considere este fragmento de código C:
Es posible que espere que s y t contengan 0:0, 1:1, 2:2, 3:3. Y u debería permanecer en 0. En cambio, el resultado se ve así:
Espera. ¿Qué pasó con “t[0]”? ¿Y por qué “u” ahora es 5?
El problema es un clásico desbordamiento de búfer. Mire de cerca la condición del bucle: i<=4. La matriz t se declara como t[4], lo que significa que tiene índices 0, 1, 2 y 3. Cuando i llega a 4, el código escribe en t[4]. Ese índice no existe dentro del espacio asignado para "t". Escribe un elemento más allá del final de la matriz.
Debido a que la memoria es contigua, la computadora coloca s, t y u uno al lado del otro en el montón o pila.
Cuando escribes más allá del final de una matriz, no fallas inmediatamente. Sobrescribes lo que esté en la ranura de memoria justo al lado.
En este caso específico, escribir en t[4] sobrescribe la ubicación de memoria que contiene u (que originalmente era 0). ¿Pero por qué estás

Escribe en s[4]. Ese índice no existe. Pero a la computadora no le importa. No comprueba si te estás pisando los propios pies. Simplemente calcula la dirección. Y esa dirección aterriza en t[0].
Crees que estás escribiendo a "s". En realidad estás escribiendo en "t". El sistema ejecuta el comando sin parpadear. La lógica es errónea. El programa corrompe los datos. Está en silencio hasta que deja de serlo.
Ahora intenta algo peor.
s[1000000] = 5;
Estás escribiendo en una memoria que tu programa no posee. Las consecuencias dependen completamente del sistema operativo que esté ejecutando. En sistemas protegidos como UNIX, Windows NT o Windows 98, el sistema operativo detecta esta infracción inmediatamente. Termina el programa. Parada dura. Protege al resto del sistema de su error.
Los sistemas más antiguos como Windows 3.1 o el Mac OS clásico no pueden darse ese lujo. No saben lo que estás haciendo. Terminas sobrescribiendo código o variables en otra aplicación. ¿El resultado? Un fallo. Un choque. O peor aún, una falla en todo el sistema que parece aleatoria pero que en realidad es el resultado directo de un acceso no controlado a la memoria.
En la memoria, variables como i, s, t y u se encuentran una al lado de la otra en direcciones específicas. Son vecinos. Si escribes más allá del límite de uno, pasas al siguiente. La computadora hace exactamente lo que dijiste. Simplemente no importa que hayas tenido buenas intenciones.
El peligro del acceso no controlado a la matriz
C y C++ no realizan verificación de rango. Asumen que sabes lo que estás haciendo. Esto significa que debes prestar mucha atención a los rangos de matrices. Si lees o escribes fuera de los límites, el comportamiento es incorrecto. Imprevisible. Peligroso.
Esta falta de seguridad es la razón por la que los errores de puntero no inicializados son tan comunes en C. Considere este código:
El compilador imprime el valor en p y la dirección de i. Inicialmente, p contiene basura o cero. La dirección de "i" suele ser un número grande. Por ejemplo, es posible que vea:
Después de p = &i, p contiene la dirección de i. Simple.
Ahora mira esto:
Esto imprime el valor al que apunta p. Pero "p" no está inicializado. Apunta a la dirección 0 o algún bit aleatorio de memoria. El resultado casi siempre es un error de segmentación. Un error de tiempo de ejecución. Estás intentando acceder a una memoria que no te pertenece.
Consejos bajo una nueva luz
Una vez que comprenda estos riesgos, las sugerencias cambian. No son sólo variables. Son direcciones directas a ubicaciones de memoria.
Toma este programa:
Esto es lo que está pasando:
Cómo los punteros almacenan direcciones de memoria
Cuando declaras una variable entera como i, ocupa 4 bytes de espacio en la RAM. Un puntero, llamémoslo p, también ocupa 4 bytes. Este tamaño es válido para la gran mayoría de los sistemas actualmente en circulación, donde las direcciones de memoria tienen una longitud de 32 bits. La industria está cambiando lentamente hacia el direccionamiento de 64 bits, pero por ahora, la huella de 4 bytes es el estándar.
Piensa en i como una casa. Tiene una dirección postal específica, digamos 248.440. El puntero p no es la casa en sí. Es una hoja de papel con esa dirección escrita. Cuando asignas p = &i, estás escribiendo 248,440 en ese papel.
Esta distinción es fundamental para comprender cómo funcionan los punteros en C. El puntero no contiene el valor de i. Contiene la ubicación donde vive i. Si elimina la referencia a p (usando p *), irá a esa dirección y leerá el valor. En ese sentido, p y i * son funcionalmente idénticos. Apuntan a los mismos datos.
Ver la dirección en código
Puede verificar este comportamiento con una simple declaración impresa.
printf("%d", p);
Este comando no imprime el valor de i. Imprime el número escrito en el papel. La salida es la dirección de memoria real de i.
¿Por qué esto importa? Porque le permite pasar ubicaciones en lugar de copias de datos. Puedes manipular la memoria directamente. Puede vincular estructuras entre sí. Puede administrar la memoria del montón de manera eficiente. El puntero es una referencia. La variable es el contenido. Confundir los dos genera errores. Comprender la diferencia conduce al control.
Algunos desarrolladores tratan los punteros como magia. No lo son. Son sólo números. Las direcciones de memoria son números enteros. Trátalos como tales.

















