En los artículos anteriores de esta serie hemos hecho un repaso y descripción de los diferentes contenedores de datos definidos en el lenguaje de programación C. Por un lado, los tipos de datos básicos y su representación en los mapas de memoria de los dispositivos. Por otro, diferentes formas de agrupar estos tipos de datos.
En este artículo vamos a profundizar la representación de todos estos bloques en los mapas de memoria. Esta representación se gestiona con direcciones de memoria. Lo que denominamos punteros.
Un puntero es por tanto una variable que contiene en su valor la dirección de la memoria donde empieza a guardarse el contenido de una variable. Sea de un tipo básico o un conjunto de datos. Se almacena en hexadecimal y su tamaño depende de la arquitectura de la memoria del Sistema Operativo (como las arquitecturas de 32 y 64 bytes de los ordenadores).
En una arquitectura de direccionamientos de longitud 4 bytes (16 bytes por línea de datos), la declaración en lenguaje C de un puntero de tipo entero, estamos definiendo un nuevo contenedor de datos cuyo contenido será tratado como una dirección de memoria. El tipo de dato definido indica la cantidad de bytes que debe utilizar el compilador para obtener el valor.
Las referencias se indican añadiendo el caracter & delante del identificador de la variable a la que se desea referencias. Esta acción indica al compilador que debe trabajar con la dirección de memoria y no con el contenido de ese identificador.
La asignación de una referencia a una variable ya declarada sería como sigue.

Pero si se modifica el tipo, en este caso que el valor es solo un byte no sufriría modificaciones

Es muy muy importante entender que, al trabajar con punteros, no se trabaja con valores, sino con las direcciones de memoria. Con el mapeado. Y que, si se reasigna directamente el valor de una variable al puntero, se está modificando dicha dirección a zonas posiblemente inaccesibles de memoria.
Si se incrementa o decrementa el valor de un puntero, el efecto es como si se desplazara una casilla de inicio a la izquierda o la derecha en el mapa de memoria. Por eso nos podemos desplazar directamente por un array mediante punteros.

Incluso arrays de diferentes dimensiones.

También podemos hacer punteros que referencien a otros punteros o punteros dobles. Esto lo indicamos incrementando el número de asteriscos en la declaración.

Por otro lado, cuando queremos acceder a los datos que están contenidos en la memoria o modificarlos, nos referimos a ellos incluyendo un asterisco justo delante del identificador del puntero.

Es recomendable encerrar el identificador entre paréntesis para indicar si un desplazamiento posterior debe aplicarse al contenido o a la propia dirección de memoria. No es lo mismo *ptr + 1 que *(ptr + 1).
También podemos manejar las direcciones como numerales hexadecimales para imprimir valores y posiciones de memoria como se ve en el siguiente ejemplo de uso de printf con diferentes situaciones.

Referencias:
- Material de clase preparado para las asignaturas de Programación impartidas en los grados de UFV
