Volver a Labs
Avanzado

Punteros en C

3–4 horas Actualizado Junio 2026
C Punteros Memoria Sistemas Bajo Nivel

Introducción

Los punteros son la razón por la que C es tan potente — y también la razón por la que tantos programadores los temen. Un puntero es simplemente una variable que almacena una dirección de memoria. Entenderlos cambia la forma en que piensas sobre cómo funciona un programa.

Son el mecanismo detrás de arrays dinámicos, listas enlazadas, árboles, paso de parámetros por referencia y la gestión manual de memoria. Sin punteros, C sería un lenguaje ordinario.

¿Qué vas a aprender en este lab?

  • Qué es un puntero y cómo declararlo
  • Operadores & (dirección) y * (desreferencia)
  • Relación entre punteros y arrays
  • Aritmética de punteros
  • Punteros a funciones
  • Gestión dinámica de memoria (malloc, free)
  • Errores más comunes y cómo evitarlos

Requisitos previos: fundamentos de C (variables, tipos, funciones, arrays). Este es un tema avanzado — tómate tu tiempo.


1. ¿Qué es un puntero?

Cada variable vive en una dirección de memoria. Un puntero almacena esa dirección.

#include <stdio.h>

int main(void) {
    int x = 42;
    int *p = &x;   /* p almacena la dirección de x */

    printf("Valor de x:          %d\n",  x);    /* 42 */
    printf("Dirección de x:      %p\n",  &x);   /* 0x7ffee4b1234c (ejemplo) */
    printf("Valor de p (direc.): %p\n",  p);    /* misma dirección */
    printf("Valor apuntado (*p): %d\n",  *p);   /* 42 */

    /* Modificar x a través del puntero */
    *p = 100;
    printf("x ahora vale:        %d\n",  x);    /* 100 */

    return 0;
}

Los dos operadores clave:

Operador Nombre Qué hace
&x Dirección-de Devuelve la dirección de memoria de x
*p Desreferencia Accede al valor en la dirección que guarda p

Declaración de punteros

int    *pi;    /* puntero a int */
double *pd;    /* puntero a double */
char   *pc;    /* puntero a char (también string) */
void   *pv;    /* puntero genérico — puede apuntar a cualquier tipo */
int    **pp;   /* puntero a puntero a int */

Siempre inicializa los punteros. Un puntero no inicializado apunta a memoria aleatoria — usarlo es comportamiento indefinido.

int *p = NULL;   /* puntero nulo — seguro: claramente "no apunta a nada" */

if (p != NULL) {
    *p = 42;  /* solo desreferencia si no es NULL */
}

2. Punteros y funciones

El uso más común de punteros es pasar variables por referencia para que una función pueda modificarlas.

#include <stdio.h>

/* Intercambia los valores de dos enteros */
void intercambiar(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

/* Devuelve múltiples valores "de salida" */
void dividir(int num, int den, int *cociente, int *resto) {
    *cociente = num / den;
    *resto    = num % den;
}

int main(void) {
    int x = 10, y = 20;
    printf("Antes: x=%d, y=%d\n", x, y);   /* 10, 20 */
    intercambiar(&x, &y);
    printf("Después: x=%d, y=%d\n", x, y); /* 20, 10 */

    int c, r;
    dividir(17, 5, &c, &r);
    printf("17 / 5 = %d con resto %d\n", c, r);  /* 3 con resto 2 */

    return 0;
}

3. Punteros y arrays

En C, el nombre de un array es un puntero a su primer elemento.

int numeros[] = {10, 20, 30, 40, 50};
int *p = numeros;   /* equivale a &numeros[0] */

printf("%d\n", *p);        /* 10 */
printf("%d\n", numeros[2]); /* 30 */
printf("%d\n", *(p + 2));  /* 30 — aritmética de punteros */

/* Las dos notaciones son equivalentes */
printf("%d\n", p[3]);      /* 40 */
printf("%d\n", *(p + 3));  /* 40 */

Aritmética de punteros

Cuando sumas n a un puntero, avanza n × sizeof(tipo) bytes — no n bytes.

int arr[]   = {1, 2, 3, 4, 5};
int *inicio = arr;
int *fin    = arr + 5;   /* apunta al elemento DESPUÉS del último */

for (int *p = inicio; p < fin; p++) {
    printf("%d ", *p);   /* 1 2 3 4 5 */
}

/* Diferencia entre punteros = número de elementos entre ellos */
printf("\nElementos: %td\n", fin - inicio);  /* 5 */

Pasar arrays a funciones

Cuando pasas un array a una función, se convierte en puntero — el tamaño se pierde.

/* Estas dos declaraciones son equivalentes: */
void imprimir(int *arr,  int n);
void imprimir(int arr[], int n);  /* azúcar sintáctico */

/* Siempre pasa el tamaño como parámetro separado */
void imprimir(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

/* sizeof NO funciona dentro de la función — ya es puntero */
void incorrecto(int arr[]) {
    int n = sizeof(arr) / sizeof(arr[0]);  /* INCORRECTO: sizeof de puntero */
}

Ejercicio 1

Escribe estas funciones que trabajen con arrays usando aritmética de punteros (sin usar arr[i], solo *(arr + i) o moviendo el puntero):

  • int suma(int *arr, int n) — suma todos los elementos
  • void revertir(int *arr, int n) — invierte el array en su lugar
  • int *buscar(int *arr, int n, int valor) — devuelve puntero al elemento o NULL si no existe

4. Punteros a strings

Un string en C es un char * que apunta al primer carácter de una secuencia terminada en \0.

char  str1[] = "Hola";   /* array en stack — modificable */
char *str2   = "Mundo";  /* puntero a literal — NO modificable en la práctica */

/* Recorrer con puntero */
for (char *p = str1; *p != '\0'; p++) {
    putchar(*p);
}
putchar('\n');

/* Implementación de strlen con punteros */
size_t mi_strlen(const char *s) {
    const char *p = s;
    while (*p) p++;       /* avanza hasta el \0 */
    return p - s;         /* diferencia = longitud */
}

Ejercicio 2

Implementa las siguientes funciones de string usando solo aritmética de punteros:

  • char *mi_strchr(const char *s, int c) — puntero a la primera ocurrencia de c en s, o NULL
  • void mi_strrev(char *s) — invierte el string en su lugar
  • int mi_atoi(const char *s) — convierte "42" a 42 (ignora espacios iniciales, maneja signo)

5. Gestión dinámica de memoria

Con arrays estáticos, debes conocer el tamaño en tiempo de compilación. La memoria dinámica te permite allocar en tiempo de ejecución.

#include <stdio.h>
#include <stdlib.h>   /* malloc, calloc, realloc, free */

int main(void) {
    int n;
    printf("¿Cuántos números? ");
    scanf("%d", &n);

    /* malloc — allocar n enteros sin inicializar */
    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Error: sin memoria\n");
        return 1;
    }

    /* Usar el array */
    for (int i = 0; i < n; i++) {
        arr[i] = i * i;  /* cuadrados */
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    /* SIEMPRE libera la memoria al terminar */
    free(arr);
    arr = NULL;  /* evita usar un puntero colgante */

    return 0;
}

malloc vs calloc vs realloc

/* malloc — allocar sin inicializar (más rápido) */
int *a = malloc(10 * sizeof(int));

/* calloc — allocar e inicializar a 0 */
int *b = calloc(10, sizeof(int));

/* realloc — cambiar el tamaño de un bloque existente */
int *c = malloc(5 * sizeof(int));
c = realloc(c, 10 * sizeof(int));  /* ahora tiene espacio para 10 */

Implementar un vector dinámico

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int    *datos;
    size_t  tamanio;
    size_t  capacidad;
} Vector;

Vector *vector_crear(void) {
    Vector *v = malloc(sizeof(Vector));
    v->capacidad = 4;
    v->tamanio   = 0;
    v->datos     = malloc(v->capacidad * sizeof(int));
    return v;
}

void vector_push(Vector *v, int valor) {
    if (v->tamanio == v->capacidad) {
        v->capacidad *= 2;
        v->datos = realloc(v->datos, v->capacidad * sizeof(int));
    }
    v->datos[v->tamanio++] = valor;
}

int vector_get(const Vector *v, size_t i) {
    return v->datos[i];
}

void vector_destruir(Vector *v) {
    free(v->datos);
    free(v);
}

6. Punteros a funciones

En C, una función también tiene una dirección. Puedes guardarla en un puntero y llamarla dinámicamente.

#include <stdio.h>

int sumar(int a, int b)  { return a + b; }
int restar(int a, int b) { return a - b; }
int multiplicar(int a, int b) { return a * b; }

/* Tipo: puntero a función que recibe dos int y devuelve int */
typedef int (*OperacionBinaria)(int, int);

void aplicar(int a, int b, OperacionBinaria op, const char *nombre) {
    printf("%s(%d, %d) = %d\n", nombre, a, b, op(a, b));
}

int main(void) {
    OperacionBinaria ops[] = { sumar, restar, multiplicar };
    const char *nombres[]  = { "sumar", "restar", "multiplicar" };

    for (int i = 0; i < 3; i++) {
        aplicar(10, 3, ops[i], nombres[i]);
    }
    /* sumar(10, 3) = 13
       restar(10, 3) = 7
       multiplicar(10, 3) = 30 */

    return 0;
}

qsort — ordenamiento genérico con puntero a función

#include <stdlib.h>

int comparar_enteros(const void *a, const void *b) {
    return *(int*)a - *(int*)b;  /* ascendente */
}

int numeros[] = {5, 2, 8, 1, 9, 3};
int n = sizeof(numeros) / sizeof(numeros[0]);

qsort(numeros, n, sizeof(int), comparar_enteros);
/* numeros ahora es {1, 2, 3, 5, 8, 9} */

Ejercicio 3

Implementa una función map que aplique una función a cada elemento de un array:

void map(int *arr, int n, int (*f)(int));

Y una función filter que copie solo los elementos que cumplan un predicado:

int filter(int *src, int n, int *dst, int (*pred)(int));
/* devuelve cuántos elementos pasaron el filtro */

Úsalas para doblar todos los elementos y para quedarte solo con los pares.


7. Errores Comunes

Error 1 — Segmentation Fault: desreferenciar NULL

int *p = NULL;
*p = 42;  /* Segmentation fault — intenta escribir en dirección 0 */

/* Verifica antes de desreferenciar */
if (p != NULL) *p = 42;

Error 2 — Puntero colgante (dangling pointer)

int *p = malloc(sizeof(int));
*p = 42;
free(p);
/* p sigue apuntando a la memoria liberada — usarla es UB */

printf("%d\n", *p);  /* comportamiento indefinido */

/* Asigna NULL después de free */
free(p);
p = NULL;

Error 3 — Memory leak (olvidar free)

void procesar(int n) {
    int *arr = malloc(n * sizeof(int));
    /* ... procesamiento ... */
    return;  /* falta free(arr) — memoria nunca liberada */
}

/* */
void procesar(int n) {
    int *arr = malloc(n * sizeof(int));
    /* ... procesamiento ... */
    free(arr);
}

Usa valgrind para detectar memory leaks:

gcc -g programa.c -o programa
valgrind --leak-check=full ./programa

Error 4 — Off-by-one en aritmética de punteros

int arr[] = {1, 2, 3};
int *fin = arr + 3;   /* apunta al elemento DESPUÉS de arr[2] */

/* Desreferenciar fin es UB */
printf("%d\n", *fin);

/* fin solo se usa como límite de comparación */
for (int *p = arr; p < fin; p++) {
    printf("%d ", *p);
}

Resumen

Concepto Sintaxis
Declarar puntero int *p;
Dirección de variable &x
Desreferenciar *p
Puntero nulo NULL
Puntero a función int (*f)(int, int)
Allocar memoria malloc(n * sizeof(T))
Liberar memoria free(p); p = NULL;
Avanzar puntero p++ o p + n

Próximos pasos

  • Estructuras (struct): crear tus propios tipos y combinarlos con punteros
  • Listas enlazadas: la primera estructura de datos dinámica con punteros
  • Árbol binario de búsqueda: punteros recursivos en estructuras