Una estructura se puede pasar/devolver por valor o pasar/devolver por referencia (a través de un puntero) en C.
El consenso general parece ser que el primero se puede aplicar a estructuras pequeñas sin penalización en la mayoría de los casos. Consulte ¿Hay algún caso en el que devolver una estructura directamente sea una buena práctica? y ¿Hay alguna desventaja en pasar estructuras por valor en C, en lugar de pasar un puntero?
Y que evitar una desreferencia puede ser beneficioso tanto desde el punto de vista de la velocidad como de la claridad. Pero, ¿qué cuenta como pequeño ? Creo que todos podemos estar de acuerdo en que esta es una estructura pequeña:
struct Point { int x, y; };Que podemos pasar por valor con relativa impunidad:
struct Point sum(struct Point a, struct Point b) { return struct Point { .x = ax + bx, .y = ay + by }; } Y que task_struct de Linux es una estructura grande:
Que nos gustaría evitar poner la pila a toda costa (¡especialmente con esas pilas de modo kernel de 8K!). Pero, ¿qué pasa con los medianos? Supongo que las estructuras más pequeñas que un registro están bien. Pero ¿qué pasa con estos?
typedef struct _mx_node_t mx_node_t; typedef struct _mx_edge_t mx_edge_t; struct _mx_edge_t { char symbol; size_t next; }; struct _mx_node_t { size_t id; mx_edge_t edge[2]; int action; };¿Cuál es la mejor regla general para determinar si una estructura es lo suficientemente pequeña como para que sea segura pasarla por valor (sin circunstancias atenuantes, como una recursividad profunda)?
Por último, por favor, no me digas que necesito perfilar. Estoy pidiendo una heurística para usar cuando soy demasiado perezoso/no vale la pena investigar más a fondo.
EDITAR: tengo dos preguntas de seguimiento basadas en las respuestas hasta ahora:
¿Qué pasa si la estructura es en realidad más pequeña que un puntero a ella?
¿Qué pasa si una copia superficial es el comportamiento deseado (la función llamada realizará una copia superficial de todos modos)?
EDITAR: No estoy seguro de por qué esto se marcó como un posible duplicado, ya que en realidad vinculo la otra pregunta en mi pregunta. Estoy pidiendo una aclaración sobre lo que constituye una pequeña estructura y soy muy consciente de que la mayoría de las veces las estructuras deben pasarse por referencia.
En arquitecturas integradas pequeñas (8/16 bits), siempre pase por puntero, ya que las estructuras no triviales no encajan en registros tan pequeños, y esas máquinas generalmente también carecen de registros.
En arquitecturas similares a PC (procesadores de 32 y 64 bits), está bien pasar una estructura por valor siempre que sizeof(mystruct_t) <= 2*sizeof(mystruct_t*) y la función no tenga muchas (generalmente más de 3 palabras de máquina). valor de) otros argumentos. En estas circunstancias, un compilador de optimización típico pasará/devolverá la estructura en un registro o par de registros. Sin embargo, en x86-32, este consejo debe tomarse con cautela, debido a la extraordinaria presión de registro con la que debe lidiar un compilador x86-32: pasar un puntero puede ser aún más rápido debido a la reducción del derrame y llenado de registros.
Devolver una estructura por valor en PC, por otro lado, sigue la misma regla, excepto por el hecho de que cuando una estructura se devuelve por puntero, la estructura que se debe completar también se debe pasar por puntero; de lo contrario , la persona que llama y la persona que llama tienen que ponerse de acuerdo sobre cómo administrar la memoria para esa estructura.
Mi experiencia, casi 40 años de embebido en tiempo real, los últimos 20 usando C; es que la mejor manera es pasar un puntero.
En cualquier caso, se debe cargar la dirección de la estructura, luego se debe calcular el desplazamiento para el campo de interés...
Al pasar toda la estructura, si no se pasa por referencia, entonces
Existen consideraciones similares para cuando una estructura se devuelve por valor.
Sin embargo, las estructuras "pequeñas", que se pueden mantener completamente en un registro de trabajo para dos, se pasan en esos registros, especialmente si se usan ciertos niveles de optimización en la declaración de compilación.
Los detalles de lo que se considera "pequeño" dependen del compilador y de la arquitectura de hardware subyacente.
Dado que la parte de paso de argumento de la pregunta ya está respondida, me centraré en la parte de retorno.
En mi opinión, lo mejor que puede hacer es no devolver estructuras o punteros a estructuras, sino pasar un puntero a la 'estructura de resultado' a la función.
void sum(struct Point* result, struct Point* a, struct Point* b);Esto tiene las siguientes ventajas:
result puede vivir en la pila o en el montón, a discreción de la persona que llama.