Punteros

Intermedio
C
C
Actualizado: 23/09/2025

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 = &numero;  // 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 - Autor del tutorial

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