Puede señalar casi cualquier cosa en C. El lenguaje le permite crear punteros para tipos primitivos, matrices, funciones y tipos definidos por el usuario. Pero las cosas se ponen interesantes en los indicadores de estructuras. Son extremadamente comunes. Si está escribiendo código C, es probable que los encuentre con frecuencia.
Aquí hay un ejemplo básico. Muestra cómo se define una estructura y luego se crea un puntero a ella.
Este fragmento hace más que simplemente asignar una dirección de memoria. Establece una relación entre la variable ptr y la estructura p1. El puntero contiene la dirección de la estructura. Le permite acceder a los miembros x e y indirectamente. Este es un patrón fundamental. Lo ve en listas vinculadas, asignación de memoria dinámica y argumentos de funciones.
¿Por qué utilizar punteros a estructuras?
Copiar estructuras enteras es costoso. Si su estructura contiene muchos datos, pasarlos por valor a una función significa que el compilador hace una copia completa. Esto hace perder tiempo y memoria. El uso de un puntero evita la copia. En su lugar, pasas la dirección. La función puede modificar los datos originales sin gastos generales.
También está la cuestión del tamaño dinámico. Las matrices tienen tamaños fijos en el momento de la compilación. Las estructuras no necesariamente resuelven por sí solas el problema dinámico. Pero cuando combinas una estructura con “malloc”, obtienes un almacenamiento de datos flexible. Puede asignar una estructura en el montón. Un puntero a esa estructura le permite administrar su vida útil manualmente.
Acceso a miembros mediante punteros
Una vez que tenga un puntero, deberá acceder a los datos que contiene. No puede utilizar el operador de punto. La expresión ptr.x no se podrá compilar. ptr es una dirección, no la estructura en sí. Primero debes eliminar la referencia.
Hay dos formas de hacer esto. El primero es detallado.
Los paréntesis son obligatorios aquí. El operador de punto tiene mayor prioridad que el operador de desreferencia. Sin ellos, el compilador intenta acceder a ptr.x, que no existe.
La segunda forma es más limpia. C proporciona el operador de flecha ->. Combina la desreferenciación y el acceso de miembros en un solo paso.
Esta sintaxis es estándar. Es lo que verá en bases de código reales. Se lee naturalmente como “vaya al objeto señalado por ptr y obtenga su miembro x “. Es conciso. Reduce el desorden visual.
Errores comunes
Los punteros nulos son un riesgo. Si declara un puntero pero no lo inicializa, apunta a basura. Acceder a ptr->x en un puntero nulo provoca un error de segmentación. Debe verificar si hay nulo antes de usar el puntero.
Esta verificación no es opcional en código robusto. Previene accidentes.
Otro problema son las pérdidas de memoria. Si asigna memoria con malloc y pierde el puntero, esa memoria permanece reservada hasta que finalice el programa. Debe realizar un seguimiento de cada asignación. free() es tu responsabilidad. Olvídalo
Los punteros a estructuras en C a menudo hacen tropezar a los desarrolladores que se sienten cómodos con los punteros básicos pero se enredan en la sintaxis. Considere una definición de registro simple para un perfil de usuario.
estructura typedef {
nombre de carácter[21];
ciudad de carbón[21];
estado de carbón [3];
} Grabación;
typedef Rec *RecPointer;
Aquí, RecPointer es solo un alias para un puntero a una estructura Rec. Cuando declaras una variable de este tipo, estás tratando con una dirección de memoria, no con los datos en sí.
RecPointer r;
La variable r ocupa cuatro bytes en un sistema de 32 bits (u ocho en un sistema de 64 bits). Es sólo un indicador. No contiene el nombre, ciudad o estado. Para almacenar datos reales, debe asignar memoria en el montón.
r = (RecPointer)malloc(tamañode(Rec));
Esta llamada malloc reserva 45 bytes. Esto equivale a 21 bytes para el nombre, 21 para la ciudad y 3 para el estado, más un byte para relleno o alineación para cumplir con los límites de la memoria. Ahora r apunta a un bloque de memoria válido que se comporta exactamente como una estructura Rec.
Acceso a miembros mediante desreferencia
Para interactuar con los datos, debe eliminar la referencia al puntero. Aquí es donde aparecen los errores de precedencia. Puede acceder a los miembros utilizando la notación de puntos estándar, pero debe incluir la desreferencia entre paréntesis.
strcpy((r).nombre, “Leigh”);
strcpy(( r).ciudad, “Raleigh”);
strcpy((*r).estado, “NC”);
Observe la sintaxis. (*r).nombre es correcto. Si escribe *r.name, no se compila. ¿Por qué? Porque el operador de punto tiene mayor prioridad que el operador de desreferencia. El compilador interpreta *r.name como *(r.name). Dado que r es un puntero, r.name tiene una sintaxis no válida y la expresión colapsa. Los paréntesis fuerzan que la desreferencia *r ocurra primero, generando la estructura, y luego el operador de punto accede al campo nombre.
Es tedioso escribir. Parece desordenado. Invita a errores.
La notación de flecha
C proporciona una forma más limpia de manejar esto. El operador de flecha -> es azúcar sintáctico para (*puntero).miembro. No es un mecanismo diferente. No es un operador nuevo que cambia la forma en que se accede a la memoria. Es simplemente una forma más corta de escribir la desreferencia y el acceso de miembros en un solo paso.
strcpy(r->nombre, “Leigh”);
Esto es idéntico a strcpy((*r).name, "Leigh"). Guarda dos personajes. Elimina la necesidad de paréntesis anidados. Es la forma estándar en que la mayoría de los desarrolladores de C interactúan con los punteros de estructura.
Implicaciones de la gestión de la memoria
La llamada free(r) es obligatoria. La memoria se asignó desde el montón. Si no lo sueltas, gotea. El puntero r en sí, la variable de cuatro bytes que contiene la dirección, es local en el marco de la pila o en el alcance global, pero los datos a los que apunta se encuentran en otra parte.
Cuando usas r->name, estás modificando los datos en la dirección almacenada en r. El puntero r permanece sin cambios. la dirección no

Asignar memoria para matrices sobre la marcha es un elemento básico de la programación en C, pero requiere comprender cómo interactúan los punteros con los bloques de memoria sin procesar. Cuando necesita una matriz de tamaño fijo que no se conoce en el momento de la compilación, la asignación de pila estándar no es suficiente. Tienes que alcanzar el montón.
El siguiente fragmento de código demuestra un patrón común:
Aquí, malloc reserva espacio para diez números enteros. La conversión a (int *) garantiza que el puntero coincida con el tipo esperado, aunque los compiladores de C modernos a menudo advierten contra conversiones explícitas para void*. Luego, el bucle inicializa cada elemento a cero usando notación de subíndice. Finalmente, “gratis” libera la memoria al sistema.
Pero esta no es la única forma de escribirlo.
Puedes lograr exactamente el mismo resultado cambiando p[i] por aritmética de punteros:
¿Por qué esto importa? Porque p[i] es solo azúcar sintáctico para *(p+i). El compilador los trata de manera idéntica. Si trabaja con sistemas integrados o escribe bucles cerrados donde cada ciclo cuenta, saber que son intercambiables le ayuda a leer el código heredado y escribir el suyo propio sin confusión.
Aún así, hay una trampa sutil.
Si declaras un puntero a un tipo de matriz directamente, como int (*p)[10], estás tratando con una bestia completamente diferente. Ese puntero apunta a la matriz completa, no solo al primer elemento. Incrementarlo mueve el puntero por el tamaño de toda la matriz, no por un solo número entero. La mayoría de los desarrolladores se quedan con int * porque es más simple y flexible.
Cuándo utilizar qué enfoque
La elección entre notación de subíndices y aritmética de punteros explícita a menudo se reduce a la legibilidad y la intención.
- Utilice
p[i]cuando desee enfatizar el acceso basado en índices. Es más claro para la mayoría de los lectores. - Utilice
*(p+i)cuando esté realizando manipulación de memoria de bajo nivel o necesite evitar peculiaridades de descomposición de matrices en expresiones complejas.
Ambos enfoques requieren una gestión cuidadosa de la memoria. Olvídese de “gratis” y tendrá fugas. Llámelo demasiado pronto y se encontrará con un comportamiento indefinido. Y aunque “malloc” es sencillo aquí, siempre verifique que el valor de retorno no sea “NULL” antes de eliminar la referencia.
“Los punteros a matrices son poderosos, pero exigen disciplina. Un paso en falso y estarás leyendo basura o, peor aún, corrompiendo la memoria de otra variable”.
En la práctica, la mayoría de los desarrolladores rara vez necesitan escribir aritmética de punteros sin formato para matrices simples. Bibliotecas como std::vector en C++ o abstracciones de nivel superior en otros lenguajes manejan esto automáticamente. ¿Pero en C? Estás solo.
Y por eso es importante comprender la mecánica. No sólo para pasar entrevistas o escribir libros de texto, sino también para cuando el código se rompe a las 2 de la madrugada y necesitas saber exactamente qué está pasando en la memoria.

Cuando declaras un puntero a una matriz de enteros, no estás creando nada exótico. Es solo un puntero estándar a un “int”. La magia ocurre con malloc. Asigna un bloque de memoria lo suficientemente grande para todos los números enteros que necesite. Luego, el puntero apunta al primer elemento de ese bloque.
A C no le importa cómo accedes a él. Puede usar corchetes como p[5] o usar aritmética de punteros como *(p + 5). El compilador los trata como idénticos. Esta flexibilidad es la razón por la que las matrices dinámicas son tan útiles para las cadenas. No adivinas el tamaño. Asigna exactamente suficiente almacenamiento para la longitud de la cadena más el terminador nulo.
Conjuntos de punteros frente a conjuntos de estructuras
¿Por qué utilizar una serie de punteros cuando podría utilizar simplemente una serie de estructuras? Espacio. O mejor dicho, la falta de ella.
Considere una estructura Rec con tres matrices de caracteres de 81 bytes cada una. Eso es 243 bytes por registro. Si declara Registros de grabación [10], reserva instantáneamente 2430 bytes en la memoria. Todo ello. Incluso si solo usas un registro.
Una serie de punteros cambia las matemáticas.
La matriz “a” en sí misma solo contiene 10 punteros. En un sistema de 64 bits, son 80 bytes. Esa es una fracción de la memoria necesaria para las estructuras completas. La memoria de los registros reales permanece sin utilizar hasta que la necesite.
Puede asignar un único registro bajo demanda.
Este patrón resuelve problemas que consumen mucha memoria al diferir la asignación. Sólo pagas por lo que usas. Cuando haya terminado con el registro, llame “gratis”. El puntero se convierte en una referencia colgante, pero la memoria se devuelve al sistema.
Estructuras que contienen punteros
Las estructuras pueden contener punteros. Esto le permite mezclar datos de tamaño fijo con datos de tamaño variable en el mismo objeto.
Tome una entrada en la libreta de direcciones. El nombre, la ciudad y el número de teléfono pueden tener una longitud máxima razonable. Un comentario, sin embargo, puede ser cualquier cosa, desde una sola palabra hasta una novela. No querrás desperdiciar espacio asignando un búfer enorme para cada entrada en caso de que una persona escriba un comentario largo.
La estructura Addr en sí es pequeña

Cómo manejan los campos de comentarios los registros vacíos frente a los registros llenos
No todos los registros de la base de datos llevan un comentario. Cuando un campo se deja en blanco, no queda vacío. Tiene un puntero. Específicamente, un puntero de 4 bytes que no apunta a nada sustancial. El sistema trata esta ausencia como un estado válido. El registro aún está completo. Los metadatos están intactos.
Pero, ¿qué sucede cuando un usuario escribe algo?
La asignación cambia. La base de datos no reserva un búfer fijo para estos comentarios. No calcula una longitud máxima y rellena el resto con bytes nulos. Eso desperdiciaría espacio. En cambio, el sistema calcula la longitud exacta de la cuerda. Luego asigna exactamente esa cantidad de bytes.
Esta asignación dinámica es eficiente. Previene la fragmentación por hinchazón. Una nota breve ocupa unos pocos bytes. Un ensayo largo requiere más. El puntero en el registro en blanco apunta a una referencia nula, manteniendo la huella mínima. Los registros con contenido se extienden para ajustarse a los datos. No se desperdicia nada. Nada es forzado.
¿Es esta la única forma de almacenar texto? No. Pero es una forma inteligente de equilibrar la velocidad y el espacio. Obtiene la capacidad de respuesta de los encabezados de tamaño fijo con la flexibilidad de los datos de longitud variable. Es un pequeño detalle. Un mecánico de bajo nivel. Pero se suma cuando se administran millones de registros. La base de datos respira más tranquila. El uso del disco se mantiene reducido.
¿Y el usuario? Nunca ven el puntero. Simplemente ven su comentario. O la falta de uno. La complejidad está oculta. El almacenamiento está optimizado. El resultado es un sistema que se siente liviano, incluso cuando los datos aumentan.
































