Saltar al contenido

Punteros I: el concepto que define a C

Direcciones, dereferencia y aritmética de punteros. El tema que más asusta y el que más poder da: una vez lo interiorizas, C entero cobra sentido.

Si hay un tema que separa a quien “sabe algo de C” de quien lo entiende, son los punteros. No son magia ni una dificultad artificial: son, sencillamente, direcciones de memoria. Y como en C todo vive en memoria, los punteros están en todas partes. Domínalos aquí.

  • Qué es un puntero (una dirección) y por qué existen.
  • Los operadores & (dirección de) y * (contenido de).
  • Aritmética de punteros.
  • void *, nullptr y la seguridad al dereferenciar.

Un puntero es una dirección

La memoria es una secuencia enorme de bytes, cada uno con una dirección (un número). Una variable normal guarda un valor; un puntero guarda la dirección de otra variable.

C
int x = 42;        // x vive en alguna dirección, digamos 0x7ffe...int *p = &x;       // p guarda la dirección de x

Dos operadores hacen todo el trabajo:

  • &x — “la dirección de x”.
  • *p — “el contenido en la dirección que guarda p” (dereferenciar).
C
printf("%d\n", x);    // 42 (el valor)printf("%p\n", (void*)&x); // la dirección de xprintf("%d\n", *p);   // 42 (dereferenciando p llegamos a x)*p = 100;             // escribir a través del punteroprintf("%d\n", x);    // 100 — cambiamos x a través de p

El modelo mental que lo desbloquea todo

Imagina la memoria como una calle de casas numeradas. Una variable int x = 42 es una casa (dirección 0x100) con un 42 dentro. Un puntero int *p = &x es un papelito donde escribiste “0x100” — no contiene el 42, contiene dónde está el 42. *p es ir a esa dirección y mirar dentro. Con este modelo, todo encaja: pasar un puntero a una función es darle la dirección para que edite tu casa; un array es una fila de casas contiguas; nullptr es un papelito con “dirección cero”, que no apunta a ninguna casa válida. Cuando “ves” la memoria así, los punteros dejan de dar miedo y se vuelven tu herramienta más poderosa.

Tipos de puntero

Un puntero tiene tipo: int * apunta a un int, char * a un char. El tipo importa porque le dice a C cuántos bytes leer al dereferenciar y cómo avanzar en la aritmética.

C
int  *pi;     // apunta a un int (lee/escribe 4 bytes al dereferenciar)char *pc;     // apunta a un char (1 byte)double *pd;   // apunta a un double (8 bytes)

nullptr y punteros no inicializados

Un puntero que no apunta a nada válido debe ser nullptr (C23) — nunca lo dejes sin inicializar:

C
int *p = nullptr;         // no apunta a nada, explícito y seguroif (p != nullptr) *p = 5; // comprueba antes de dereferenciarint *malo;                // MAL: puntero "salvaje": apunta a basura// *malo = 5;             // comportamiento indefinido: crash o corrupción

Dereferenciar mal es el pecado capital de C

Los tres errores que causan la mayoría de los crashes y vulnerabilidades en C: dereferenciar un puntero nulo, uno no inicializado (salvaje), o uno colgante (que apunta a memoria ya liberada). Ninguno da un error amable: dan corrupción de memoria o crash, a veces mucho después del fallo real. La disciplina que te salva: inicializa siempre a nullptr, comprueba antes de dereferenciar, y pon a nullptr tras liberar. Los sanitizers (nivel 24) cazan estos errores; pero la mejor defensa es el hábito.

Aritmética de punteros

Sumar a un puntero no avanza bytes: avanza elementos del tipo apuntado. Esto conecta punteros y arrays:

C
int nums[3] = {10, 20, 30};int *p = nums;        // p apunta al primer elementoprintf("%d\n", *p);   // 10p++;                  // avanza UN int (4 bytes), no un byteprintf("%d\n", *p);   // 20printf("%d\n", *(p + 1)); // 30 — un elemento más allá

*(p + i) es exactamente lo que hace nums[i] por debajo. Los índices de array son azúcar sintáctico sobre aritmética de punteros (lo verás en el nivel 9).

void *: el puntero genérico

void * apunta a “algo” sin tipo. Es el puntero genérico de C — lo usa malloc, y lo verás en APIs genéricas. No puedes dereferenciarlo directamente (no sabe el tamaño): primero conviertes a un tipo concreto.

C
void *generico = &x;int *como_int = generico;   // convertir antes de usarprintf("%d\n", *como_int);

Interioriza los punteros

  1. Declara un int, un puntero a él con &, y modifica el int a través de *p.
  2. Imprime la dirección de una variable con %p.
  3. Recorre un array con un puntero y aritmética (p++, *(p+i)) en vez de índices.
  4. Comprueba un puntero contra nullptr antes de dereferenciar.
  5. Guarda una dirección en un void * y conviértela de vuelta para usarla.