Una estructura que se pasa a una función se copia. Eso está bien para un punto de dos ints y es un derroche para un registro de 200 bytes, y además hace imposible la mutación, porque la función solo ve su propia copia. Apuntar a una estructura resuelve ambos problemas a la vez, y es también la única forma de construir estructuras que crecen: listas, árboles, grafos.
Tomar un puntero a una estructura
No hace falta nada especial. & te da la dirección, y el tipo es "puntero a estructura":
Los tres imprimen 3. Los dos últimos son la misma operación escrita de dos formas.
Por qué (*ptr).x necesita sus paréntesis
Podrías intentar quitarlos y escribir *ptr.x. Eso compila a algo bastante distinto, o más a menudo se niega a compilar, porque el operador de miembro . tiene mayor precedencia que la desreferencia *:
*ptr.x // se analiza como *(ptr.x) -- mal
(*ptr).x // desreferencia primero y luego toma el miembro -- correcto
ptr->x // lo mismo, dicho corto
ptr.x pide un miembro del puntero, y un puntero no tiene miembros. La queja del compilador ("request for member 'x' in something not a structure or union") es un error de precedencia disfrazado.
Como esa forma con paréntesis es fea y fácil de equivocar, C provee ->. En la práctica casi nunca verás (*p).x en código real; recurre a p->x y olvida que la forma larga existe, salvo como explicación de lo que significa la flecha.
Pasar una estructura por puntero
Un parámetro de tipo puntero le da a la función la estructura real de quien llama, así que los cambios se quedan:
Dos hábitos que vale la pena formar aquí:
- Marca
constlos punteros de solo lectura.const struct Point *ppromete que la función no cambiará aquello a lo que apuntap. Si alguien después agrega unp->x = 0dentro, la compilación falla en lugar de que el error llegue a producción. Además documenta la intención en el punto de llamada. - Pasa un puntero para estructuras grandes aunque solo leas. Una estructura con un búfer de nombre de 64 bytes cuesta 64 bytes de copia por llamada si va por valor; un puntero cuesta 8.
Las estructuras pequeñas siguen estando bien por valor: una struct Point de 8 bytes se copia tan barato como un puntero, y la versión por valor no puede ser NULL, lo que elimina todo un modo de fallo.
Punteros dentro de arrays de estructuras
La aritmética de punteros funciona sobre arrays de estructuras exactamente igual que sobre arrays de int: p + 1 avanza una estructura entera, relleno incluido.
El nombre de un array decae a un puntero a su primer elemento, así que staff ya es un struct Employee * en la llamada. Es también por eso que la función necesita que se le pase n aparte: sizeof dentro de la función mediría el puntero, no el array.
Reservar una estructura en el montón
Las estructuras de la pila mueren al final de su ámbito. Para hacer una que sobreviva a la función que la creó —o para hacer tantas como exija la entrada— resérvala con malloc:
Cuatro detalles de esa pequeña función son todos portantes:
sizeof *e, nosizeof(struct Employee). Se lee como "el tamaño de aquello a lo que apuntae", así que si el tipo cambia alguna vez la reserva lo sigue automáticamente. No hay forma de que los dos se desincronicen.- Comprueba si es
NULL.mallocdevuelveNULLcuando no puede satisfacer la petición. Escribire->ida través de un puntero nulo es un fallo de segmentación. - Inicializa cada miembro.
mallocno pone la memoria a cero; la estructura llega llena de basura. (calloc(1, sizeof *e)la pone a cero por ti.) - Alguien debe hacerle
free. Devolver un puntero reservado transfiere ese deber a quien llama. Dilo en un comentario: una regla de propiedad poco clara es como empiezan las fugas de memoria.
La recompensa: un nodo de lista enlazada
Aquí está lo que las estructuras no pueden hacer sin punteros. Una estructura no puede contenerse a sí misma —eso sería infinitamente grande— pero sí puede contener un puntero a una de su propia clase, y ese único truco construye todas las estructuras de datos enlazadas de C.
Fíjate en que struct Node *next; se refiere a struct Node mientras struct Node todavía se está declarando. Eso es legal precisamente porque un puntero tiene un tamaño conocido sin importar a qué apunte: el compilador todavía no necesita la definición completa. Es también el único lugar donde debes usar la etiqueta struct Node aunque además hayas escrito un typedef: el nombre del typedef aún no existe dentro de su propia definición.
El bucle de free_list guarda head->next antes de llamar a free(head). Leer head->next después de liberar es un uso tras liberación: la memoria ya no es tuya, y el valor que lees es lo que el asignador haya puesto ahí.
Errores comunes
p->xsobre un puntero sin inicializar oNULL. La causa más común de un fallo en código con muchas estructuras. Inicializa los punteros aNULLy compruébalos antes de desreferenciar.- Devolver un puntero a una estructura local.
struct Point *bad(void) { struct Point p = {1,2}; return &p; }devuelve la dirección de memoria que deja de existir en el momento en que la función retorna. Devuelve la estructura por valor, o resérvala. - Liberar dos veces, u olvidar liberar. Cada
mallocse empareja con exactamente unfree. Tras liberar, pon el puntero aNULLpara que un uso accidental posterior reviente ruidosamente en lugar de corromper memoria en silencio. - Copiar una estructura que contiene un puntero. La asignación de estructuras copia el valor del puntero, no aquello a lo que apunta. Ambas estructuras referencian ahora el mismo búfer, y la que lo libere primero deja colgando a la otra.
Preguntas frecuentes
¿Qué hace el operador -> en C?
-> en C?p->x accede al miembro x a través del puntero p. Es exactamente equivalente a (*p).x: desreferencia el puntero y luego toma el miembro. La flecha existe porque los punteros a estructuras son tan comunes que la forma larga ensucia el código.
¿Por qué *p.x no funciona con un puntero a estructura?
*p.x no funciona con un puntero a estructura?Porque . se asocia más fuerte que *. *p.x se analiza como *(p.x): toma el miembro x de p (que es un puntero, no una estructura) y desreferencia eso. Necesitas (*p).x, o simplemente escribe p->x.
¿Debería pasar una estructura por valor o por puntero en C?
Pasa por puntero cuando la función deba modificar la estructura de quien llama, o cuando la estructura sea grande y copiarla sea un derroche. Pasa por valor las estructuras pequeñas de solo lectura, donde la copia es barata y el código es más simple. Marca los parámetros de puntero de solo lectura como const struct T * para que el compilador haga cumplir la promesa.
¿Cómo se reserva una estructura con malloc en C?
struct Node *n = malloc(sizeof *n);: la forma sizeof *n pide el tamaño de aquello a lo que apunta n, así que sigue siendo correcta si después cambias el tipo. Comprueba el resultado contra NULL, inicializa cada miembro y haz free(n) al terminar.