Punteros en C — direcciones de memoria sin misterio
Los punteros en C son el tema que más miedo da en IC2, y el que más diferencia a C de cualquier otro lenguaje que hayas visto antes. En Python nunca piensas en memoria, simplemente asignas valores y Python se encarga del resto. En C tú eres el responsable de saber dónde está cada dato en memoria y cómo acceder a él.
En este artículo desmontamos los punteros desde cero, qué es una dirección de memoria, qué significan * y &, y por qué los vectores y los punteros son en realidad lo mismo.
Tabla de Contenidos
Por qué Python no tiene punteros — y C sí
En Python cuando escribes x = 5 Python crea el objeto 5 en memoria, apunta x hacia él y se encarga de todo lo demás. Tú nunca ves la dirección de memoria donde está ese 5.
En C cuando escribes int x = 5 reservas exactamente 4 bytes en memoria con una dirección específica, por ejemplo 0x7ffd1234. Tú puedes acceder a esa dirección directamente y manipularla. Eso es un puntero, una variable que guarda una dirección de memoria.
/* Python — no ves la memoria */ x = 5 y = x /* y apunta al mismo objeto */ /* C — tú controlas la memoria */ int x = 5; int *ptr = &x; /* ptr guarda la dirección de x */
Esta diferencia es lo que hace C tan potente y tan peligroso a la vez, tienes control total sobre la memoria pero también toda la responsabilidad.
Qué es una dirección de memoria
Imagina la memoria RAM como una calle con casas numeradas. Cada casa (byte) tiene un número de puerta único, eso es la dirección de memoria. Cuando declaras una variable en C le reservas una o varias casas consecutivas en esa calle.
int x = 5; /* reserva 4 casas (4 bytes) */ char c = 'A'; /* reserva 1 casa (1 byte) */ float f = 3.14; /* reserva 4 casas (4 bytes) */
Cada variable tiene una dirección, puedes verla con el operador &:
#include <stdio.h>
int main() {
int x = 5;
char c = 'A';
float f = 3.14;
printf("Valor de x: %d\n", x);
printf("Dirección de x: %p\n", &x);
printf("Dirección de c: %p\n", &c);
printf("Dirección de f: %p\n", &f);
return 0;
}
Valor de x: 5 Dirección de x: 0x7ffd5a1234bc Dirección de c: 0x7ffd5a1234b8 Dirección de f: 0x7ffd5a1234b4
Las direcciones exactas varían cada vez que ejecutas el programa, el sistema operativo decide dónde colocar las variables. Lo importante es el concepto: cada variable tiene una dirección única.
Declarar un puntero
Un puntero es una variable que guarda una dirección de memoria:
tipo * nombre_puntero;
El * en la declaración indica que es un puntero, no es una multiplicación. El tipo indica qué tipo de dato hay en la dirección que guarda:
int *ptr_entero; /* puntero a int */ char *ptr_caracter; /* puntero a char */ float *ptr_flotante; /* puntero a float */
Los dos operadores clave — & y *
Este es el punto que más confunde. En C & y * tienen dos usos cada uno:
El operador &, dirección de:
int x = 5; int *ptr = &x; /* & da la dirección de x */ /* ↑ "dame la dirección de memoria donde está x" */
El operador *, desreferenciar (acceder al valor):
int x = 5;
int *ptr = &x;
printf("%d\n", *ptr); /* * accede al valor en la dirección */
/* ↑
"dame el valor que hay en la dirección que guarda ptr" */
La tabla que lo aclara todo:
int x = 5; int *ptr = &x; /* &x → dirección de x (ej: 0x7ffd1234) */ /* ptr → también la dirección de x */ /* *ptr → valor en esa dirección = 5 */ /* x → también el valor = 5 */ /* ptr == &x → verdad (misma dirección) */ /* *ptr == x → verdad (mismo valor) */
Modificar una variable a través de un puntero
#include <stdio.h>
int main() {
int x = 5;
int *ptr = &x;
printf("Antes: x = %d\n", x);
*ptr = 33; /* modifica x a través del puntero */
printf("Después: x = %d\n", x); /* x ahora vale 33 */
return 0;
}
Antes: x = 5 Después: x = 33
Cuando escribes *ptr = 33 no estás cambiando ptr, estás cambiando el valor en la dirección a la que apunta ptr. Como ptr apunta a x, estás cambiando x.

Aritmética de punteros
Puedes sumar y restar enteros a un puntero. Pero no es suma normal, suma en unidades del tipo al que apunta:
int *ptr;
int vec[5] = {10, 20, 30, 40, 50};
ptr = vec; /* ptr apunta al primer elemento */
printf("%d\n", *ptr); /* → 10 */
printf("%d\n", *(ptr+1)); /* → 20 */
printf("%d\n", *(ptr+2)); /* → 30 */
Si ptr es un puntero a int y un int ocupa 4 bytes, entonces ptr+1 no suma 1 a la dirección, suma 4 (el tamaño de un int). Esto es lo que hace posible recorrer arrays con punteros.
int vec[3] = {10, 20, 30};
int *ptr = vec;
/* ptr apunta a vec[0] — dirección ej: 1000 */
/* ptr+1 apunta a vec[1] — dirección ej: 1004 */
/* ptr+2 apunta a vec[2] — dirección ej: 1008 */

Punteros y vectores — por qué son lo mismo
Esta es la revelación más importante sobre los punteros en C. El nombre de un vector es un puntero constante al primer elemento:
int vec[5] = {10, 20, 30, 40, 50};
int *ptr = vec; /* equivale a ptr = &vec[0] */
/* Estas expresiones son equivalentes */
vec[2] == *(vec+2) /* → 30 */
ptr[2] == *(ptr+2) /* → 30 */
vec[2] == ptr[2] /* → 30 */
#include <stdio.h>
int main() {
int vec[5] = {10, 20, 30, 40, 50};
int *ptr = vec;
int i;
printf("Acceso con índice:\n");
for (i = 0; i < 5; i++) {
printf("vec[%d] = %d\n", i, vec[i]);
}
printf("\nAcceso con puntero:\n");
for (i = 0; i < 5; i++) {
printf("*(ptr+%d) = %d\n", i, *(ptr+i));
}
printf("\nRecorrido moviendo el puntero:\n");
for (ptr = vec; ptr < vec+5; ptr++) {
printf("%d ", *ptr);
}
printf("\n");
return 0;
}
Acceso con índice: vec[0] = 10 vec[1] = 20 vec[2] = 30 vec[3] = 40 vec[4] = 50 Acceso con puntero: *(ptr+0) = 10 *(ptr+1) = 20 *(ptr+2) = 30 *(ptr+3) = 40 *(ptr+4) = 50 Recorrido moviendo el puntero: 10 20 30 40 50
La diferencia clave: vec es un puntero constante, no puedes hacer vec++. ptr es un puntero normal, puedes moverlo libremente.
vec++; /* ILEGAL — vec es constante */ ptr++; /* LEGAL — ptr es una variable */
Los errores más peligrosos con punteros
Error 1 — Puntero sin inicializar
int *ptr;
*ptr = 5; /* PELIGRO — ptr apunta a basura */
/* comportamiento indefinido — puede crashear */
/* BIEN — siempre inicializa antes de usar */
int x = 5;
int *ptr = &x;
*ptr = 10; /* seguro */
Error 2 — Acceso fuera del rango del vector:
int vec[3] = {1, 2, 3};
int *ptr = vec;
printf("%d\n", *(ptr+5)); /* acceso fuera de rango */
/* comportamiento indefinido */
Error 3 — Confundir el puntero con el valor:
int x = 5;
int *ptr = &x;
printf("%d\n", ptr); /* MAL — imprime la dirección como entero */
printf("%p\n", ptr); /* BIEN — imprime la dirección correctamente */
printf("%d\n", *ptr); /* BIEN — imprime el valor */
Visualízalo con Python Tutor
Los punteros son donde Python Tutor más brilla en C, puedes ver exactamente las direcciones de memoria y cómo los punteros apuntan a variables. Ve a pythontutor.com, selecciona C y pega:
#include <stdio.h>
int main() {
int x = 15;
int *ptr = &x;
printf("x = %d\n", x);
printf("ptr apunta a: %p\n", ptr);
printf("valor en ptr: %d\n", *ptr);
*ptr = 33;
printf("x ahora vale: %d\n", x);
return 0;
}
Ejecuta paso a paso y observa cómo ptr aparece como una flecha que apunta a x en el diagrama de memoria, y cómo al cambiar *ptr cambia también x.
Resumen rápido
/* DECLARAR UN PUNTERO */
int *ptr; /* puntero a int */
char *ptr; /* puntero a char */
/* OPERADORES */
&variable /* dirección de la variable */
*puntero /* valor en la dirección del puntero */
/* USO BÁSICO */
int x = 5;
int *ptr = &x; /* ptr guarda la dirección de x */
printf("%d\n", *ptr); /* imprime el valor de x */
*ptr = 10; /* modifica x a través del puntero */
/* ARITMÉTICA */
ptr + 1 /* siguiente elemento del tipo */
ptr - 1 /* elemento anterior */
ptr++ /* avanza al siguiente */
ptr-- /* retrocede al anterior */
/* PUNTEROS Y VECTORES */
int vec[5];
int *ptr = vec; /* ptr apunta al primer elemento */
vec[i] == *(vec+i) == ptr[i] == *(ptr+i)
/* ERRORES TÍPICOS */
/* 1. Puntero sin inicializar → comportamiento indefinido */
/* 2. Acceso fuera de rango → comportamiento indefinido */
/* 3. %d en vez de %p para imprimir dirección */
/* 4. vec++ → ilegal, vec es constante */
En el próximo artículo practicamos los punteros con programas reales, recorrido de vectores, intercambio de valores y funciones que modifican variables externas.
Pointers in C — memory addresses without the mystery
Pointers in C are the topic that intimidates everyone in IC2, and what most differentiates C from any other language you’ve seen. In Python you never think about memory. In C you’re responsible for knowing where every piece of data lives in memory.
Why Python has no pointers — and C does
# Python — you never see memory x = 5 y = x # y points to the same object
/* C — you control memory */ int x = 5; int *ptr = &x; /* ptr stores x's memory address */
What is a memory address
Think of RAM as a street with numbered houses. Each house (byte) has a unique number, that’s the memory address. When you declare a variable in C you reserve one or more consecutive houses.
int x = 5; /* reserves 4 bytes */ char c = 'A'; /* reserves 1 byte */ float f = 3.14; /* reserves 4 bytes */
See an address with &:
#include <stdio.h>
int main() {
int x = 5;
printf("Value of x: %d\n", x);
printf("Address of x: %p\n", &x);
return 0;
}
Value of x: 5 Address of x: 0x7ffd5a1234bc
Exact addresses vary each run, the OS decides placement. The concept is what matters.
Declaring a pointer
type *pointer_name; int *int_ptr; /* pointer to int */ char *char_ptr; /* pointer to char */ float *float_ptr; /* pointer to float */
The two key operators — & and *
int x = 5;
int *ptr = &x; /* & gives the address of x */
printf("%d\n", *ptr); /* * accesses the value at the address */
The clarifying table:
int x = 5; int *ptr = &x; /* &x → address of x (e.g. 0x7ffd1234) */ /* ptr → also address of x */ /* *ptr → value at address = 5 */ /* x → also the value = 5 */ /* ptr == &x → true */ /* *ptr == x → true */
Modifying a variable through a pointer
#include <stdio.h>
int main() {
int x = 5;
int *ptr = &x;
printf("Before: x = %d\n", x);
*ptr = 33;
printf("After: x = %d\n", x);
return 0;
}
Before: x = 5 After: x = 33
Pointer arithmetic
int vec[5] = {10, 20, 30, 40, 50};
int *ptr = vec;
printf("%d\n", *ptr); /* → 10 */
printf("%d\n", *(ptr+1)); /* → 20 */
printf("%d\n", *(ptr+2)); /* → 30 */
ptr+1 doesn’t add 1 to the address, it adds sizeof(int) = 4 bytes.
Pointers and arrays — why they’re the same
int vec[5] = {10, 20, 30, 40, 50};
int *ptr = vec;
/* These are all equivalent */
vec[2] == *(vec+2) == ptr[2] == *(ptr+2) /* → 30 */
vec++; /* ILLEGAL — vec is constant */
ptr++; /* LEGAL — ptr is a variable */
Common dangerous mistakes
/* Mistake 1 — uninitialised pointer */
int *ptr;
*ptr = 5; /* DANGER — undefined behaviour */
int x = 5;
int *ptr = &x; /* RIGHT — always initialise */
/* Mistake 2 — out of bounds access */
int vec[3] = {1, 2, 3};
*(ptr+5); /* undefined behaviour */
/* Mistake 3 — wrong format specifier */
printf("%d\n", ptr); /* WRONG — use %p for addresses */
printf("%p\n", ptr); /* RIGHT */
printf("%d\n", *ptr); /* RIGHT — value */
Quick summary
/* DECLARE */
int *ptr;
/* OPERATORS */
&variable /* address of variable */
*pointer /* value at address */
/* BASIC USE */
int x = 5;
int *ptr = &x;
printf("%d\n", *ptr); /* prints value of x */
*ptr = 10; /* modifies x */
/* ARITHMETIC */
ptr + 1 /* next element */
ptr++ /* advance */
/* ARRAYS AND POINTERS */
vec[i] == *(vec+i) == ptr[i] == *(ptr+i)
/* COMMON MISTAKES */
/* 1. Uninitialised pointer → undefined behaviour */
/* 2. Out of bounds → undefined behaviour */
/* 3. %d instead of %p for addresses */
/* 4. vec++ → illegal */

3 comentarios