Punteros en C — teoría con direcciones, * y &, y vectores recorriendo las paradas de la guagua (y el saldo que bajó sin tocarlo)
La primera vez que vi *p = *p - 155; bajarle el saldo a mi bono de guagua sin escribir saldo en ninguna parte del programa, pensé que el compilador se había vuelto loco. No era magia: era un puntero. Y en cuanto entiendes qué guarda esa variable con asterisco, media asignatura de IC2 (memoria dinámica, cadenas, procesos, hilos) deja de dar miedo.
En este artículo desmontamos los punteros en C con calma: qué es una dirección de memoria, qué hace cada asterisco y cada &, por qué p + 1 no suma un byte, por qué un vector y un puntero se parecen tanto (y en qué no) y qué pasa de verdad cuando un bucle da una vuelta de más. Todo con el mismo ejemplo que en Bucles en C: la guagua, sus paradas y el saldo del bono en céntimos. Todas las salidas son de una ejecución real con gcc 14.
Tabla de Contenidos
Por qué Python no tiene punteros y C sí
Si vienes de Python, nunca has tenido que pensar dónde vive un dato. Escribes saldo = 1000 y Python crea un objeto, le pega una etiqueta y se ocupa de todo lo demás: reservar memoria, moverla, liberarla. Por eso, cuando alguien busca «punteros en Python», la respuesta corta es que no existen como tales: Python usa referencias, pero nunca te deja ver ni tocar la dirección.
En C es al revés. Cuando escribes int saldo = 1000; reservas 4 bytes concretos en la memoria, en una posición concreta con un número concreto. Ese número es la dirección de saldo, y C te deja leerla, guardarla en otra variable y usarla para llegar al dato por otro camino. Una variable que guarda una dirección es un puntero.
¿Por qué querría alguien eso? Por tres motivos que vas a ver una y otra vez en IC2:
- Para que una función modifique variables de quien la llama (el famoso paso por referencia, que verás a fondo en Funciones en C).
- Para recorrer vectores y cadenas moviéndote por la memoria.
- Para pedir memoria mientras el programa corre con
malloc, que solo te devuelve una dirección (Memoria dinámica en C).
Control total sobre la memoria también significa responsabilidad total. Python te avisa con una excepción cuando te sales de una lista; C no te avisa de nada. Al final del artículo verás un programa que lee memoria que no es suya y sigue tan tranquilo.
Qué es una dirección de memoria
Imagina la memoria como una línea de guagua larguísima, con una parada en cada byte y cada parada con su número. Una variable ocupa una o varias paradas seguidas: un char, una; un int, cuatro; un double, ocho. La dirección de la variable es el número de su primera parada.
El operador & delante de una variable te da su dirección, y %p la imprime en hexadecimal:
#include <stdio.h>
int main(void) {
int saldo = 1000; /* céntimos que le quedan al bono */
char linea = 'L'; /* letra de la línea */
double precio = 1.55; /* precio de un viaje, en euros */
/* %p imprime una dirección; (void *) es el tipo que %p espera */
printf("%-7s= %-5d en %p (%zu bytes)\n", "saldo", saldo, (void *)&saldo, sizeof saldo);
printf("%-7s= %-5c en %p (%zu byte)\n", "linea", linea, (void *)&linea, sizeof linea);
printf("%-7s= %-5.2f en %p (%zu bytes)\n", "precio", precio, (void *)&precio, sizeof precio);
return 0;
}
Salida:
saldo = 1000 en 0x7ffe0051225c (4 bytes) linea = L en 0x7ffe0051225b (1 byte) precio = 1.55 en 0x7ffe00512260 (8 bytes)
Tres cosas que mirar en esta salida:
- Las direcciones empiezan por
0x7ffe…: es la zona de la pila, donde viven las variables locales demain. Son números enormes porque la memoria virtual de un proceso es enorme. linea(1 byte) está en…25bysaldo(4 bytes) en…25c: justo al lado.precioestá en…260, cuatro bytes más allá desaldo. El compilador las coloca como le parece mejor, no en el orden en que las declaras.sizeofconfirma los tamaños: 4, 1 y 8 bytes.
Las direcciones cambian en cada ejecución. Linux coloca la pila en un sitio distinto cada vez (se llama ASLR y es una medida de seguridad). Si ejecutas este programa en tu Fedora verás otros números: lo que no cambia es la idea, ni las distancias entre variables de un mismo vector.
¿Por qué (void *)&saldo y no &saldo a secas? Porque %p espera un puntero genérico (void *). Con &saldo también suele funcionar, pero la conversión explícita es lo correcto y gcc no tiene nada que reprocharte.
Declarar un puntero: el * de la declaración
Un puntero se declara con el tipo del dato al que va a apuntar y un asterisco:
int *p; /* p guardará la dirección de un int */ char *pc; /* pc, la de un char */ double *pd; /* pd, la de un double */
Ese asterisco no es una multiplicación ni un operador: forma parte del tipo. int *p se lee «p es un puntero a int». El tipo importa por dos razones: le dice al compilador cuántos bytes leer cuando vayas a la dirección y cuánto avanzar cuando hagas p + 1 (lo verás en la aritmética).
Aquí está la trampa que cae en todos los exámenes. ¿Cuántos punteros declara esta línea?
#include <stdio.h>
int main(void) {
int saldo = 1000, viajes = 6;
int* p, q; /* parece que declara dos punteros... */
p = &saldo;
q = &viajes; /* ...pero q es un int normal */
printf("%d %d\n", *p, q);
return 0;
}
Uno solo. El asterisco se pega a la variable, no al tipo: int* p, q; es lo mismo que int *p; int q;. Por eso gcc 14 se niega a compilar:
declarar.c: In function 'main': declarar.c:7: error: assignment to 'int' from 'int *' makes integer from pointer without a cast [-Wint-conversion]
Te está diciendo que intentas meter una dirección (int *) en un int. Si quieres dos punteros, cada uno lleva su asterisco: int *p, *q;. Por eso en C se suele escribir el asterisco pegado al nombre: así se ve a quién pertenece.
int* p, q; declara un puntero y un entero. Con gcc 14 (el de Fedora) es un error de compilación en cuanto asignas una dirección a q; con compiladores antiguos era solo un aviso y el programa hacía cosas raras.
Los dos operadores clave: & y *
Con el puntero declarado, entran en juego dos operadores que son exactamente opuestos:
| Operador | Se lee | Le das | Te devuelve |
|---|---|---|---|
&x | «dirección de x» | una variable | su dirección |
*p | «lo apuntado por p» | una dirección | el dato que hay allí |
Míralo con el bono de la guagua:
#include <stdio.h>
int main(void) {
int saldo = 1000; /* 10,00 euros en céntimos */
int *p = &saldo; /* p guarda la dirección de saldo */
printf("&saldo = %p\n", (void *)&saldo);
printf("p = %p\n", (void *)p); /* la misma dirección */
printf("*p = %d\n", *p); /* lo que hay en esa dirección */
*p = *p - 155; /* pago un viaje A TRAVÉS de p */
printf("Tras picar el bono: saldo = %d, *p = %d\n", saldo, *p);
/* & y * se deshacen el uno al otro */
printf("*&saldo = %d, &*p = %p\n", *&saldo, (void *)&*p);
return 0;
}
Salida:
&saldo = 0x7ffc726ab67c p = 0x7ffc726ab67c *p = 1000 Tras picar el bono: saldo = 845, *p = 845 *&saldo = 845, &*p = 0x7ffc726ab67c
Vamos línea a línea:
int *p = &saldo;declarapy en la misma línea le guarda la dirección desaldo. Aquí el*es el de la declaración (tipo) y el&el operador «dirección de».&saldoypimprimen la misma dirección:pes una copia de ese número.*pvale 1000: el asterisco en una expresión significa «ve a esa dirección y trae lo que haya». Eso se llama desreferenciar.*p = *p - 155;lee 1000 a través dep, le resta el viaje y escribe el resultado en esa misma dirección. Como esa dirección es la desaldo,saldopasa a valer 845 aunque no aparezca en la línea.- La última línea demuestra que se deshacen:
*&saldoes «lo que hay en la dirección de saldo», o sea,saldo. Y&*pes «la dirección de lo apuntado por p», o sea,p.
Fíjate en el dibujo: la caja de p no contiene un 1000, contiene dónde está el 1000. Toda la gracia de los punteros cabe en esa flecha.
Modificar una variable a través de un puntero
Lo que acabas de ver con *p = *p - 155; es la operación estrella. Cuando escribes *p = valor; no cambias p (sigue apuntando al mismo sitio); cambias lo que hay donde apunta. Si quisieras que p apuntara a otra variable, escribirías p = &otra; sin asterisco.
La tabla que conviene tener delante hasta que salga sola:
| Escribes | Qué cambia | Ejemplo |
|---|---|---|
p = &otra; | a dónde apunta p | p deja de señalar saldo |
*p = 690; | el dato apuntado | saldo pasa a 690 |
int *p = &saldo; | declara y apunta a la vez | el * es del tipo |
Confundir el asterisco de la declaración con el de la expresión. En int *p = &saldo; NO estás haciendo *p = &saldo (guardar una dirección en un int); estás declarando p y dándole valor. Por eso es más claro en dos líneas: int *p; y p = &saldo;.
El puntero NULL y el puntero sin inicializar
Un puntero recién declarado sin valor contiene basura: los bits que hubiera en esa memoria. Si lo usas, escribes en una dirección cualquiera. Para decir «este puntero todavía no apunta a nada» existe NULL, una dirección especial (la 0) que el sistema operativo no deja tocar.
#include <stdio.h>
int main(void) {
int *bono = NULL; /* todavía no apunta a ningún bono */
if (bono == NULL) /* también vale: if (!bono) */
printf("Sin bono: no puedo leer el saldo\n");
printf("Voy a leerlo igualmente...\n");
printf("Saldo: %d\n", *bono); /* desreferenciar NULL */
return 0;
}
Salida:
Sin bono: no puedo leer el saldo Voy a leerlo igualmente... Segmentation fault (core dumped)
El if (bono == NULL) hace su trabajo; luego, a propósito, desreferenciamos NULL y el sistema mata el proceso con Segmentation fault: el programa intentó leer una dirección que no le pertenece. Es el error más famoso de C y casi siempre significa «has usado un puntero que no apuntaba a nada válido». Los apuntes de IC2 lo comprueban con if (ptr) y if (!ptr), que es lo mismo que comparar con NULL.
¿Y si no lo inicializas en absoluto? Es peor, porque ni siquiera es seguro que falle:
#include <stdio.h>
int main(void) {
int *p; /* sin inicializar: guarda lo que hubiera ahí */
*p = 155; /* ¿dónde escribe esto? nadie lo sabe */
printf("Escrito\n");
return 0;
}
gcc con -Wall lo ve venir:
sin_iniciar.c: In function 'main': sin_iniciar.c:5: warning: 'p' is used uninitialized [-Wuninitialized] sin_iniciar.c:4: note: 'p' was declared here
Y aun así, en mi ejecución el programa imprimió esto y terminó «bien»:
Escrito
Escribió 155 en algún sitio de la memoria y nadie se quejó. En otra ejecución, o en el ordenador del laboratorio, puede romperse o corromper otra variable. Ese «a veces funciona» es lo que hace tan traicioneros los punteros sin inicializar.
Inicializa siempre los punteros al declararlos: con una dirección válida (int *p = &saldo;) o con NULL si todavía no la tienes. Un NULL falla en el acto y te dice dónde; la basura falla más tarde y en otro sitio.
Aritmética de punteros
A un puntero le puedes sumar y restar enteros, pero no son sumas normales: p + 1 avanza un elemento del tipo al que apunta, no un byte. Compruébalo con tres vectores de tipos distintos:
#include <stdio.h>
int main(void) {
int subidas[5] = {12, 7, 9, 4, 15}; /* viajeros que suben en cada parada */
char linea[5] = "L201"; /* 4 letras + '\0' */
double km[5] = {0.0, 1.2, 2.9, 4.1, 6.0}; /* kilómetro de cada parada */
int *pi = subidas; /* lo mismo que &subidas[0] */
char *pc = linea;
double *pd = km;
for (int i = 0; i < 3; i++) {
printf("+%d: pi=%p pc=%p pd=%p\n", i,
(void *)(pi + i), (void *)(pc + i), (void *)(pd + i));
}
printf("*(pi + 2) = %d, igual que subidas[2] = %d\n", *(pi + 2), subidas[2]);
printf("(pi + 4) - pi = %td elementos\n", (pi + 4) - pi);
return 0;
}
Salida:
+0: pi=0x7ffd86d0ccc0 pc=0x7ffd86d0cd13 pd=0x7ffd86d0cce0 +1: pi=0x7ffd86d0ccc4 pc=0x7ffd86d0cd14 pd=0x7ffd86d0cce8 +2: pi=0x7ffd86d0ccc8 pc=0x7ffd86d0cd15 pd=0x7ffd86d0ccf0 *(pi + 2) = 9, igual que subidas[2] = 9 (pi + 4) - pi = 4 elementos
Mira las últimas cifras de cada columna:
pi(puntero aint):…cc0,…cc4,…cc8. Sube de 4 en 4.pc(puntero achar):…d13,…d14,…d15. Sube de 1 en 1.pd(puntero adouble):…ce0,…ce8,…cf0. Sube de 8 en 8 (en hexadecimal,ce8 + 8 = cf0).
El compilador multiplica por ti: pi + 2 es «la dirección de pi más 2 × sizeof(int)». Por eso *(pi + 2) vale 9, igual que subidas[2]. Y al revés: restar dos punteros del mismo vector da elementos, no bytes: (pi + 4) - pi son 4 (el %td es el formato del tipo ptrdiff_t, el de las restas de punteros).
Solo tiene sentido sumar o restar punteros dentro de un mismo vector. Restar las direcciones de dos variables sueltas compila, pero el resultado no significa nada.
Punteros y vectores: por qué son casi lo mismo
Esta es la revelación que lo cambia todo: cuando usas el nombre de un vector en una expresión, C lo convierte en la dirección de su primer elemento. int *p = subidas; es exactamente int *p = &subidas[0];. Y el operador [] no es más que aritmética de punteros con otra cara: subidas[i] significa *(subidas + i).
Por eso estas tres formas de recorrer el vector dan lo mismo:
#include <stdio.h>
int main(void) {
int subidas[5] = {12, 7, 9, 4, 15};
int *p = subidas;
printf("Con índice: ");
for (int i = 0; i < 5; i++) printf("%d ", subidas[i]);
printf("\nCon *(p + i): ");
for (int i = 0; i < 5; i++) printf("%d ", *(p + i));
printf("\nMoviendo p: ");
for (p = subidas; p < subidas + 5; p++) printf("%d ", *p);
printf("\n");
p = subidas; /* vuelvo al principio */
printf("p[3] = %d y 3[subidas] = %d\n", p[3], 3[subidas]);
printf("sizeof subidas = %zu, sizeof p = %zu\n", sizeof subidas, sizeof p);
return 0;
}
Salida:
Con índice: 12 7 9 4 15 Con *(p + i): 12 7 9 4 15 Moviendo p: 12 7 9 4 15 p[3] = 4 y 3[subidas] = 4 sizeof subidas = 20, sizeof p = 8
Las tres primeras líneas son idénticas. La cuarta trae una curiosidad: como p[3] es *(p + 3) y la suma es conmutativa, 3[subidas] es *(3 + subidas), que también compila y da 4. No lo escribas nunca en un examen, pero demuestra que los corchetes son pura aritmética.
¿Entonces vector y puntero son lo mismo? Casi. La última línea enseña la primera diferencia: sizeof subidas da 20 (5 enteros × 4 bytes, el vector entero) y sizeof p da 8 (lo que ocupa una dirección en un sistema de 64 bits). La segunda diferencia es que el nombre del vector no se puede mover:
int main(void) {
int subidas[5] = {12, 7, 9, 4, 15};
subidas++; /* quiero pasar a la segunda parada */
return 0;
}
vec_mas.c: In function 'main': vec_mas.c:3: error: lvalue required as increment operand
«lvalue required» quiere decir que subidas no es una variable que puedas cambiar: el vector está donde está. Si quieres avanzar, usa un puntero (p++), que sí es una variable.
| Expresión | Vector subidas | Puntero p = subidas |
|---|---|---|
[i] y *(… + i) | Sí, iguales | Sí, iguales |
++ / -- / = | No: error de compilación | Sí |
sizeof | todo el vector (20) | la dirección (8) |
Calcular cuántos elementos tiene un vector con sizeof v / sizeof v[0] dentro de una función que lo recibe como parámetro. Allí v ya es un puntero y sizeof v da 8, no el tamaño del vector. Por eso las funciones reciben siempre el tamaño aparte (lo verás en la práctica y en Funciones en C).
La vuelta de más: lo que te prometí en Bucles en C
En Bucles en C te dije que la vuelta de más de un for con <= «lee memoria que no es tuya». Ahora que sabes que v[i] es *(v + i), puedes verlo con tus propios ojos. El bono guarda lo que costaron los 4 viajes de la semana, y el bucle va de 0 a 4 incluido:
#include <stdio.h>
int main(void) {
int bono[4] = {155, 155, 140, 140}; /* lo que costó cada viaje de la semana */
int total = 0;
int *p = bono;
for (int i = 0; i <= 4; i++) { /* <= : una vuelta de más */
printf("viaje %d: %d\n", i, *(p + i));
total += *(p + i);
}
printf("Total: %d céntimos\n", total);
return 0;
}
Salida:
viaje 0: 155 viaje 1: 155 viaje 2: 140 viaje 3: 140 viaje 4: 641088528 Total: 641089118 céntimos
El «viaje 4» no existe: p + 4 señala la memoria que hay justo detrás del vector, y * lee lo que haya allí, que en esta ejecución era 641088528. El programa ni se queja ni se para, y suma esa basura al total. Eso es lo peor que puede pasar: un fallo silencioso que da un resultado absurdo con toda la tranquilidad del mundo.
Para cazarlo existe AddressSanitizer (ASan), una herramienta de gcc que vigila cada acceso a memoria. Se activa compilando así:
gcc -Wall -std=c11 -g -fsanitize=address vuelta.c -o vuelta
El mismo programa, compilado con ASan:
viaje 0: 155
viaje 1: 155
viaje 2: 140
viaje 3: 140
=================================================================
==702==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fca61900030
READ of size 4 at 0x7fca61900030 thread T0
#0 in main vuelta.c:9
Address 0x7fca61900030 is located in stack of thread T0 at offset 48 in frame
#0 in main vuelta.c:3
[32, 48) 'bono' (line 4) <== Memory access at offset 48 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow vuelta.c:9 in main
Ahora sí: los cuatro viajes buenos salen y, en el quinto acceso, ASan para el programa y te dice qué ha pasado (stack-buffer-overflow: te has salido de un vector de la pila), en qué línea (vuelta.c:9, la del printf) y qué variable has desbordado ('bono'): el acceso al byte 48 se sale de las casillas 32-48 que ocupa bono.
Cuando un programa con vectores o punteros dé resultados raros, vuelve a compilarlo con -g -fsanitize=address. Va más lento, pero te señala la línea exacta del primer acceso indebido. En las prácticas de IC2 ahorra horas de buscar a ciegas.
Cadenas: punteros a char y vectores de punteros
En C no hay tipo «cadena»: una cadena es un vector de char que termina en el carácter nulo '\0'. Y como es un vector, se recorre con un puntero exactamente igual que los números, solo que el final no lo marca un tamaño, sino ese '\0'.
#include <stdio.h>
int main(void) {
char destino[] = "Teror"; /* 5 letras + '\0' */
const char *paradas[] = {"San Telmo", "Tamaraceite", "Teror"};
int letras = 0;
printf("sizeof destino = %zu\n", sizeof destino);
for (char *p = destino; *p != '\0'; p++) { /* hasta el carácter nulo */
printf("%c", *p);
letras++;
}
printf(" tiene %d letras\n", letras);
for (int i = 0; i < 3; i++) /* cada casilla guarda una dirección */
printf("paradas[%d] = %-11s empieza en %p\n", i, paradas[i], (void *)paradas[i]);
return 0;
}
Salida:
sizeof destino = 6 Teror tiene 5 letras paradas[0] = San Telmo empieza en 0x5603093ca008 paradas[1] = Tamaraceite empieza en 0x5603093ca012 paradas[2] = Teror empieza en 0x5603093ca01e
sizeof destinoda 6, no 5: «Teror» son 5 letras más el'\0'que C añade solo. Es lo mismo que pasa en los apuntes de IC2 consizeof(cad)de «prueba», que da 7.- El
foravanzapletra a letra y se para cuando*pes el nulo. Ese patrón (*p != '\0') es el corazón destrlen,strcpyy todas las funciones de<string.h>. paradases un vector de punteros: cada casilla no guarda un texto, guarda la dirección donde empieza un texto. Sus direcciones (0x5603…) no se parecen a las de la pila: son de la zona de datos de solo lectura del programa, donde el compilador deja las cadenas literales. Por eso las declaramosconst char *: esas letras no se pueden modificar.
Olvidar el sitio para el '\0'. char linea[4] = "L201"; compila, pero no deja hueco para el nulo y cualquier función que la recorra seguirá leyendo memoria hasta encontrar un cero por casualidad. Para 4 letras hacen falta 5 casillas.
Puntero a puntero
Si un puntero es una variable, también tiene dirección, y esa dirección se puede guardar en otro puntero. Un puntero a puntero se declara con dos asteriscos:
#include <stdio.h>
int main(void) {
int saldo = 845;
int *p = &saldo;
int **pp = &p; /* puntero a un puntero a int */
printf("saldo = %d, *p = %d, **pp = %d\n", saldo, *p, **pp);
**pp = 690; /* otro viaje de 1,55 euros, a dos saltos */
printf("saldo ahora = %d\n", saldo);
printf("&saldo = %p, *pp = %p\n", (void *)&saldo, (void *)*pp);
return 0;
}
Salida:
saldo = 845, *p = 845, **pp = 845 saldo ahora = 690 &saldo = 0x7ffd7ec3ea74, *pp = 0x7ffd7ec3ea74
**pp hace dos saltos: *pp es p (la dirección de saldo) y **pp es lo que hay allí, el saldo. Por eso **pp = 690; le cobra otro viaje. ¿Dónde aparece esto en IC2? En char *argv[] de main, que es un vector de punteros (y se puede escribir char **argv), y en funciones que necesitan cambiar a dónde apunta un puntero de quien las llama, como verás en memoria dinámica.
Errores típicos con punteros
Un resumen de lo que más rompe programas en el laboratorio, de más a menos frecuente:
- Usar un puntero sin inicializar. Inicialízalo siempre: a una dirección válida o a
NULL. - Desreferenciar
NULL. Antes de*p, compruebaif (p != NULL)si el puntero viene de una búsqueda o demalloc. - Salirse del vector.
<=donde iba<, o un puntero que pasa del final. Compila con ASan cuando dudes. - Imprimir una dirección con
%do%x. Los apuntes de IC2 lo hacen con%x, pero en un sistema de 64 bits la dirección no cabe en uninty gcc avisa. Lo correcto es%pcon(void *). int* p, q;creyendo que son dos punteros.*p++cuando querías(*p)++. El++va antes que el*: avanza el puntero, no el valor. Lo verás en «¿Qué falla aquí?» de los ejercicios.
Míralo paso a paso en Python Tutor
Python Tutor ejecuta el código línea a línea y dibuja la memoria: cada variable en su caja y cada puntero como una flecha. Con punteros es donde más luce. Entra, elige C en el desplegable del lenguaje y pega este programa (datos fijos, sin scanf):
#include <stdio.h>
int main(void) {
int saldo = 1000;
int *p = &saldo;
*p = *p - 155;
int subidas[4] = {12, 7, 9, 4};
int total = 0;
for (int *q = subidas; q < subidas + 4; q++) {
total += *q;
}
printf("saldo = %d, total = %d\n", saldo, total);
return 0;
}
Qué observar paso a paso:
- Tras
int *p = &saldo;, la caja depno muestra un número: muestra una flecha hastasaldo. Esa flecha es el dibujo de la dirección. - En
*p = *p - 155;el valor que cambia es el desaldo(de 1000 a 845); la flecha depsigue igual. - Al declarar
subidasverás cuatro casillas seguidas. En elfor, la flecha deqempieza en la casilla 0 y salta una casilla en cadaq++. - En la última vuelta
qapunta detrás del vector y el bucle termina sin leerlo. Ahí está, en dibujo, por qué el límite es<y no<=.
Al final debe salir saldo = 845, total = 32 (12 + 7 + 9 + 4 = 32).
Resumen
| Quiero… | Código | Ejemplo con la guagua |
|---|---|---|
| Declarar un puntero | int *p; | puntero a un saldo |
| Guardar una dirección | p = &saldo; | p señala el bono |
| Leer lo apuntado | *p | 1000 |
| Escribir en lo apuntado | *p = *p - 155; | el saldo baja a 845 |
| Puntero que no apunta a nada | int *p = NULL; | sin bono todavía |
| Avanzar un elemento | p + 1, p++ | siguiente parada |
| Posición dentro del vector | p - subidas | número de parada |
| Recorrer una cadena | for (p = s; *p != '\0'; p++) | letras de «Teror» |
| Imprimir una dirección | printf("%p", (void *)p); | 0x7ffc… |
Tres ideas para quedarte:
- Un puntero guarda dónde está un dato; con
*vas allí y con&sacas la dirección de una variable. p + 1avanza un elemento, yv[i]es solo otra forma de escribir*(v + i).- C no vigila la memoria por ti: inicializa los punteros, respeta los límites con
<y, si algo huele raro, compila con ASan.
Ahora toca usarlo en programas completos. En la práctica de punteros en C intercambiamos el origen y el destino de la línea, recorremos sus paradas en los dos sentidos, sacamos el resumen del día con una función que devuelve varios resultados y buscamos paradas con un puntero que puede volver con NULL.
¿Te ha servido este artículo? Así puedes apoyar SergioLearns →




Qué es un sistema operativo — teoría con tu portátil en una tarde de estudio, una llamada a write() y la CPU que nunca descansa (y los tres programas que no se ejecutaban a la vez)