Fundamentos de direcciones de memoria y variables puntero
La memoria de un ordenador funciona como una inmensa serie de casillas numeradas, donde cada casilla puede almacenar un dato. Cuando declaramos una variable en C, el compilador reserva una o más casillas consecutivas en memoria para almacenar su valor. Cada casilla tiene un número único que la identifica, conocido como dirección de memoria.
Imaginemos que tenemos una variable entera llamada edad
con valor 25. Esta variable no solo tiene un valor, sino también una ubicación específica en memoria. Si esa variable se encuentra en la posición 1000 de memoria, decimos que la dirección de edad
es 1000.
Qué son los punteros
Un puntero es una variable especial que almacena direcciones de memoria en lugar de valores directos. En otras palabras, un puntero "apunta" hacia la ubicación donde se encuentra almacenado otro dato.
La declaración de un puntero sigue esta sintaxis:
tipo *nombre_puntero;
El asterisco (*
) indica que estamos declarando un puntero, no una variable normal. Por ejemplo:
int *ptr_edad; // Puntero que puede apuntar a un entero
char *ptr_letra; // Puntero que puede apuntar a un carácter
float *ptr_nota; // Puntero que puede apuntar a un número decimal
Inicialización de punteros
Los punteros no inicializados contienen direcciones aleatorias y son extremadamente peligrosos. Siempre debemos inicializarlos antes de usarlos:
#include <stdio.h>
int main() {
int numero = 42;
int *ptr = NULL; // Inicialización segura con NULL
printf("Valor de numero: %d\n", numero);
printf("Dirección de numero: %p\n", (void*)&numero);
printf("Valor del puntero ptr: %p\n", (void*)ptr);
return 0;
}
En este ejemplo, NULL
es un valor especial que indica que el puntero no apunta a ninguna dirección válida. Es una práctica recomendada inicializar siempre los punteros con NULL
hasta que les asignemos una dirección válida.
Diferencias entre variables normales y punteros
Una variable normal almacena directamente un valor:
int edad = 25; // edad contiene directamente el valor 25
Un puntero almacena la dirección donde se encuentra un valor:
int edad = 25;
int *ptr_edad = &edad; // ptr_edad contiene la dirección de edad
Esta distinción es fundamental. La variable edad
contiene el número 25, mientras que ptr_edad
contiene un número que representa una posición de memoria.
Tamaño de los punteros
Independientemente del tipo de dato al que apunten, todos los punteros tienen el mismo tamaño en una arquitectura específica. En sistemas de 64 bits, los punteros ocupan 8 bytes; en sistemas de 32 bits, ocupan 4 bytes:
#include <stdio.h>
int main() {
int *ptr_int;
char *ptr_char;
double *ptr_double;
printf("Tamaño de int*: %zu bytes\n", sizeof(ptr_int));
printf("Tamaño de char*: %zu bytes\n", sizeof(ptr_char));
printf("Tamaño de double*: %zu bytes\n", sizeof(ptr_double));
// Todos mostrarán el mismo tamaño
return 0;
}
Declaración múltiple de punteros
Al declarar múltiples punteros en una sola línea, debemos colocar el asterisco antes de cada nombre:
int *ptr1, *ptr2, *ptr3; // Tres punteros a entero
int *ptr_a, variable_b; // ptr_a es puntero, variable_b es entero normal
Esta sintaxis puede resultar confusa al principio, por lo que muchos programadores prefieren declarar cada puntero en una línea separada para mayor claridad:
int *ptr1;
int *ptr2;
int *ptr3;
Visualización conceptual
Para entender mejor los punteros, podemos pensar en este diagrama conceptual:
Memoria:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ ... │ 25 │ ... │ 1004 │ ... │
├─────────┼─────────┼─────────┼─────────┼─────────┤
│ ... │ 1004 │ ... │ 2008 │ ... │
└─────────┴─────────┴─────────┴─────────┴─────────┘
↑ ↑
edad ptr_edad
(posición 1004) (posición 2008)
En este ejemplo, edad
está en la posición 1004 y contiene el valor 25, mientras que ptr_edad
está en la posición 2008 y contiene el valor 1004 (la dirección de edad
).
Los punteros son una herramienta fundamental en C que permite un control directo sobre la memoria. Aunque pueden parecer complejos inicialmente, proporcionan flexibilidad y eficiencia que hacen de C un lenguaje especialmente útil para programación de sistemas y aplicaciones que requieren un manejo preciso de recursos.
Operadores & (dirección) y * (contenido)
Los punteros en C se manipulan principalmente a través de dos operadores fundamentales: el operador de dirección (&
) y el operador de desreferenciación (*
). Estos operadores trabajan de forma complementaria y son esenciales para el manejo efectivo de punteros.
El operador & (dirección)
El operador &
se conoce como operador de dirección o "address-of". Su función es obtener la dirección de memoria donde se almacena una variable. Cuando aplicamos &
a una variable, obtenemos un puntero hacia esa variable.
#include <stdio.h>
int main() {
int numero = 100;
printf("Valor de numero: %d\n", numero);
printf("Dirección de numero: %p\n", &numero);
return 0;
}
En este ejemplo, &numero
nos devuelve la dirección exacta donde está almacenado el valor 100. Esta dirección es lo que podemos asignar a un puntero.
Asignación usando el operador &
Para hacer que un puntero apunte a una variable específica, utilizamos el operador &
en la asignación:
#include <stdio.h>
int main() {
int edad = 25;
int *ptr_edad; // Declaración del puntero
ptr_edad = &edad; // Asignación: ptr_edad ahora apunta a edad
printf("Valor de edad: %d\n", edad);
printf("Dirección de edad: %p\n", &edad);
printf("Valor almacenado en ptr_edad: %p\n", ptr_edad);
return 0;
}
Observa cómo &edad
y ptr_edad
muestran la misma dirección. Esto confirma que el puntero está correctamente configurado.
El operador * (desreferenciación)
El operador *
tiene un doble propósito en C. Ya vimos que se usa en la declaración de punteros, pero también se utiliza para acceder al contenido de la dirección a la que apunta un puntero. Este segundo uso se llama desreferenciación.
#include <stdio.h>
int main() {
int temperatura = 22;
int *ptr_temp = &temperatura;
printf("Valor original: %d\n", temperatura);
printf("Valor a través del puntero: %d\n", *ptr_temp);
return 0;
}
En este caso, *ptr_temp
accede al contenido de la dirección almacenada en ptr_temp
, que es exactamente el valor de temperatura
.
Modificación a través de punteros
Una de las características más útiles de los punteros es que permiten modificar variables de forma indirecta:
#include <stdio.h>
int main() {
int contador = 10;
int *ptr_contador = &contador;
printf("Valor inicial: %d\n", contador);
*ptr_contador = 50; // Modificamos contador a través del puntero
printf("Valor después de modificar: %d\n", contador);
printf("Valor accedido por puntero: %d\n", *ptr_contador);
return 0;
}
Al escribir *ptr_contador = 50
, estamos cambiando directamente el contenido de la variable contador
. Ambas formas de acceso (contador
y *ptr_contador
) muestran el mismo resultado.
Operaciones con desreferenciación
Los punteros desreferenciados se comportan exactamente como variables normales. Podemos realizar operaciones matemáticas y lógicas:
#include <stdio.h>
int main() {
int a = 15;
int b = 8;
int *ptr_a = &a;
int *ptr_b = &b;
// Operaciones usando desreferenciación
int suma = *ptr_a + *ptr_b;
*ptr_a += 5; // Incrementa 'a' en 5 unidades
printf("Suma: %d\n", suma);
printf("Nuevo valor de a: %d\n", a);
printf("Valor de a través del puntero: %d\n", *ptr_a);
return 0;
}
Diferencias entre * en declaración y desreferenciación
Es importante distinguir los dos usos del asterisco:
Uso 1 - Declaración de puntero:
int *ptr; // El * indica que ptr es un puntero a int
Uso 2 - Desreferenciación:
int valor = *ptr; // El * accede al contenido de la dirección
Ejemplo completo que muestra ambos usos:
#include <stdio.h>
int main() {
int original = 42;
int *puntero; // * en declaración
puntero = &original;
int copia = *puntero; // * en desreferenciación
printf("Valor original: %d\n", original);
printf("Valor copiado: %d\n", copia);
return 0;
}
Verificación de punteros NULL
Antes de desreferenciar un puntero, siempre debemos verificar que no sea NULL
para evitar errores:
#include <stdio.h>
int main() {
int *ptr = NULL;
int numero = 100;
if (ptr != NULL) {
printf("Valor: %d\n", *ptr);
} else {
printf("El puntero es NULL, no se puede desreferenciar\n");
}
ptr = № // Ahora ptr apunta a algo válido
if (ptr != NULL) {
printf("Ahora sí podemos acceder: %d\n", *ptr);
}
return 0;
}
Ejemplo práctico completo
Veamos un ejemplo que integra ambos operadores de forma práctica:
#include <stdio.h>
int main() {
// Variables normales
double precio = 19.99;
double descuento = 0.15;
// Punteros
double *ptr_precio = &precio;
double *ptr_descuento = &descuento;
// Cálculo usando punteros
double precio_final = *ptr_precio * (1.0 - *ptr_descuento);
printf("Precio original: %.2f€\n", precio);
printf("Descuento: %.0f%%\n", *ptr_descuento * 100);
printf("Precio final: %.2f€\n", precio_final);
// Modificar precio a través del puntero
*ptr_precio = 25.50;
printf("Nuevo precio: %.2f€\n", precio);
return 0;
}
Los operadores &
y *
forman la base fundamental del trabajo con punteros en C. El operador &
nos permite obtener direcciones, mientras que *
nos permite acceder y modificar el contenido de esas direcciones. Dominar estos operadores es esencial para aprovechar todo el potencial que ofrecen los punteros en la programación con C.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en C
Documentación oficial de C
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, C es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de C
Explora más contenido relacionado con C y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué es un puntero y cómo almacena direcciones de memoria.
- Aprender a declarar, inicializar y diferenciar punteros de variables normales.
- Entender el uso de los operadores & (dirección) y * (desreferenciación).
- Saber cómo acceder y modificar valores a través de punteros.
- Reconocer la importancia de verificar punteros NULL para evitar errores.
Cursos que incluyen esta lección
Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje