valores valor una retornan que llamar imprimir funciones funcion estructuras estructura con como asignar arreglo c shallow-copy

retornan - ¿Cuándo debería pasar o devolver una estructura por valor?



scanf estructuras c (8)

Una estructura puede pasarse / devolverse por valor o pasarse / devolverse por referencia (mediante un puntero) en C.

El consenso general parece ser que el primero se puede aplicar a pequeñas estructuras sin penalización en la mayoría de los casos. Ver ¿Hay algún caso para el cual devolver una estructura directamente sea una buena práctica? y ¿Hay algún inconveniente para pasar las estructuras por valor en C, en lugar de pasar un puntero?

Y eso evitar una desreferencia puede ser beneficioso desde una perspectiva tanto de velocidad como de 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 de valor con relativa impunidad:

struct Point sum(struct Point a, struct Point b) { return struct Point { .x = a.x + b.x, .y = a.y + b.y }; }

Y que task_struct de Linux es una gran estructura:

https://github.com/torvalds/linux/blob/b953c0d234bc72e8489d3bf51a276c5c4ec85345/include/linux/sched.h#L1292-1727

Que querríamos evitar poner la pila a toda costa (¡especialmente con esas pilas de modo kernel de 8K!). Pero, ¿qué hay de los medios? Supongo que las estructuras más pequeñas que un registro están bien. Pero, ¿qué hay de 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 empírica para determinar si una estructura es lo suficientemente pequeña como para que sea segura pasarla por su valor (salvo circunstancias extenuantes como alguna recursión profunda)?

Por último, no me digas que necesito perfil. Estoy pidiendo una heurística para usar cuando soy demasiado flojo / no vale la pena investigar más.

EDITAR: Tengo dos preguntas de seguimiento basadas en las respuestas hasta el momento:

  1. ¿Qué pasa si la estructura es realmente más pequeña que un puntero a ella?

  2. ¿Qué pasa si una copia superficial es el comportamiento deseado (de todos modos, la función llamada realizará una copia poco profunda)?

EDITAR: No estoy seguro de por qué esto se marcó como un posible duplicado ya que de hecho enlace la otra pregunta en mi pregunta. Estoy pidiendo aclaraciones sobre lo que constituye una pequeña estructura y estoy muy consciente de que la mayoría de las estructuras de tiempo deben pasarse por referencia.


Como ya se ha respondido la parte de discusión de los pasajes, me centraré en la parte que regresa.

Lo mejor que se puede hacer IMO es no devolver estructuras o punteros a las estructuras en absoluto, sino pasar un puntero a la ''estructura de resultados'' de la función.

void sum(struct Point* result, struct Point* a, struct Point* b);

Esto tiene las siguientes ventajas:

  • La estructura result puede vivir en la pila o en el montón, a discreción del que llama.
  • No hay problemas de propiedad, ya que está claro que la persona que llama es responsable de asignar y liberar la estructura de resultados.
  • La estructura podría incluso ser más larga de lo que se necesita, o estar integrada en una estructura más grande.

En pequeñas arquitecturas integradas (8/16-bitters), siempre pase por puntero, ya que las estructuras no triviales no caben en registros tan pequeños, y esas máquinas generalmente también están privadas de registro.

En arquitecturas similares a PC (procesadores de 32 y 64 bits), pasar una estructura por valor está bien proporcionado sizeof(mystruct_t) <= 2*sizeof(mystruct_t*) y la función no tiene muchas (generalmente más de 3 palabras de máquina) valor de) otros argumentos. En estas circunstancias, un compilador optimizador típico pasará / devolverá la estructura en un registro o par de registros. Sin embargo, en x86-32, este consejo debe tomarse con un grano de sal considerable, debido a la extraordinaria presión de registro que debe afrontar un compilador x86-32: pasar un puntero puede ser aún más rápido debido a la reducción del llenado y el llenado del registro.

Devolver una estructura por valor en PC-likes, por otro lado, sigue la misma regla, salvo por el hecho de que cuando una estructura se devuelve con un puntero, la estructura que se debe completar también debe pasarse por un puntero; de lo contrario , el llamado y la persona que llama están estancados al tener que ponerse de acuerdo sobre cómo administrar la memoria para esa estructura.


En una PC típica, el rendimiento no debería ser un problema incluso para estructuras bastante grandes (muchas docenas de bytes). En consecuencia, otros criterios son importantes, especialmente la semántica: ¿de verdad quieres trabajar en una copia? ¿O en el mismo objeto, por ejemplo, al manipular listas vinculadas? La pauta debería ser expresar la semántica deseada con la construcción de lenguaje más apropiada para que el código sea legible y mantenible.

Dicho esto, si hay algún impacto en el rendimiento, puede no ser tan claro como uno pensaría.

  • Memcpy es rápido, y la localidad de memoria (que es buena para la pila) puede ser más importante que el tamaño de datos: la copia puede ocurrir en la caché, si pasa y devuelve una estructura por valor en la pila. Además, la optimización del valor de retorno debería evitar la copia redundante de las variables locales que se devolverán (que los compiladores ingenuos hicieron hace 20 o 30 años).

  • Al pasar los punteros se introducen alias en las ubicaciones de memoria que luego no pueden almacenarse en la memoria caché de manera más eficiente. Los lenguajes modernos a menudo están más orientados al valor porque todos los datos están aislados de los efectos secundarios, lo que mejora la capacidad del compilador para optimizar.

La conclusión es sí, a menos que tenga problemas, no dude en pasar el valor si es más conveniente o apropiado. Puede ser incluso más rápido.


La forma en que una estructura pasa a una función o desde ella depende de la interfaz binaria (ABI) de la aplicación y del estándar de llamada a procedimiento (PCS, a veces incluido en ABI) para su plataforma objetivo (CPU / OS, para algunas plataformas puede haber más de una versión).

Si el PCS realmente permite pasar una estructura en registros, esto no solo depende de su tamaño, sino también de su posición en la lista de argumentos y los tipos de argumentos precedentes. ARM-PCS (AAPCS), por ejemplo, empaqueta argumentos en los primeros 4 registros hasta que estén completos y pasa más datos a la pila, incluso si eso significa que un argumento está dividido (todo simplificado, si está interesado: los documentos se pueden descargar gratis desde ARM )

Para las estructuras devueltas, si no se pasan a través de registros, la mayoría de los PCS asignan el espacio en la pila a la persona que llama y pasan un puntero a la estructura al destinatario (variante implícita). Esto es idéntico a una variable local en la persona que llama y pasa el puntero de forma explícita: para el destinatario. Sin embargo, para la variante implícita, el resultado debe copiarse a otra estructura, ya que no hay forma de obtener una referencia a la estructura implícitamente asignada.

Algunos PCS pueden hacer lo mismo para las estructuras de argumentos, otros solo usan los mismos mecanismos que para los escalares. De cualquier forma, difiere esas optimizaciones hasta que realmente sepa que las necesita. Lea también la PCS de su plataforma de destino. Recuerde que su código puede funcionar aún peor en una plataforma diferente.

Nota: el PCS moderno no utiliza pasar una estructura a través de una temperatura global, ya que no es seguro para subprocesos. Sin embargo, para algunas pequeñas arquitecturas de microcontroladores, esto podría ser diferente. Principalmente si solo tienen una pequeña pila (S08) o características restringidas (PIC). Pero para la mayoría de estas veces las estructuras no se pasan en los registros, tampoco, y se recomienda encarecidamente pasar por puntero.

Si es solo por inmutabilidad del original: pase a const mystruct *ptr . A menos que const la const que dará una advertencia al menos al escribir en la estructura. El puntero también puede ser constante: const mystruct * const ptr .

Entonces: ninguna regla general; depende de demasiados factores.


Mi experiencia, casi 40 años de integración en tiempo real, los últimos 20 con C; es que la mejor manera es pasar un puntero.

En cualquier caso, la dirección de la estructura necesita ser cargada, entonces la compensación para el campo de interés necesita ser calculada ...

Al pasar toda la estructura, si no se pasa por referencia, entonces

  1. no se coloca en la pila
  2. se copia, generalmente mediante una llamada oculta a memcpy ()
  3. se copia en una sección de la memoria que ahora está ''reservada'' y no está disponible para ninguna otra parte del programa.

Existen consideraciones similares para cuando una estructura se devuelve por valor.

Sin embargo, las estructuras "pequeñas", que se pueden retener por completo en un registro de trabajo a 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.


Nota: razones para hacerlo de una manera u otra superposición.

Cuándo pasar / devolver por valor:

  1. El objeto es un tipo fundamental como int , double , puntero.
  2. Se debe hacer una copia binaria del objeto, y el objeto no es grande.
  3. La velocidad es importante y pasar de valor es más rápido.
  4. El objeto es conceptualmente un pequeño numérico

    struct quaternion { long double i,j,k; } struct pixel { uint16_t r,g,b; } struct money { intmax_t; int exponent; }

Cuándo usar un puntero al objeto

  1. No está seguro de si el valor o un puntero al valor es mejor, por lo que esta es la opción predeterminada.
  2. El objeto es grande
  3. La velocidad es importante y pasar por un puntero al objeto es más rápido.
  4. El uso de la pila es crítico. (Estrictamente esto puede favorecer por valor en algunos casos)
  5. Se necesitan modificaciones al objeto pasado.
  6. El objeto necesita administración de memoria.

    struct mystring { char *s; size_t length; size_t size; }

Notas: Recuerde que en C, nada se pasa realmente por referencia. Incluso pasar un puntero se pasa por valor, ya que el valor del puntero se copia y se pasa.

Prefiero pasar números, ya sean int o pixel por valor, ya que es conceptualmente más fácil de entender el código. Pasar los números por dirección es conceptualmente un poco más difícil. Con objetos numéricos más grandes, puede ser más rápido pasar por la dirección.

Los objetos que tienen su dirección pasada pueden usar restrict para informar a la función que los objetos no se superponen.


Realmente, la mejor regla general, cuando se trata de pasar una estructura como argumento a una función por referencia vs por valor, es evitar pasarla por valor. Los riesgos casi siempre superan los beneficios.

En aras de la exhaustividad, señalaré que al pasar / devolver una estructura por valor suceden algunas cosas:

  1. todos los miembros de la estructura se copian en la pila
  2. si se devuelve una estructura por valor, nuevamente, todos los miembros se copian de la memoria de pila de la función a una nueva ubicación de memoria.
  3. la operación es propensa a errores; si los miembros de la estructura son punteros, un error común es suponer que puede pasar el parámetro por su valor, ya que está operando con punteros, esto puede hacer que sea muy difícil detectar errores.
  4. si su función modifica el valor de los parámetros de entrada y sus entradas son variables de estructura, pasadas por valor, debe recordar SIEMPRE devolver una variable de estructura por valor (he visto esto varias veces). Lo que significa duplicar el tiempo copiando los miembros de la estructura.

Ahora llegar a lo suficientemente pequeño significa en términos de tamaño de la estructura, para que valga la pena pasarlo por valor, eso dependería de algunas cosas:

  1. la convención de llamadas: ¿qué guarda automáticamente el compilador en la pila cuando llama a esa función (generalmente es el contenido de unos pocos registros)? Si los miembros de su estructura se pueden copiar en la pila aprovechando este mecanismo, entonces no hay penalización.
  2. el tipo de datos del miembro de estructura: si los registros de su máquina son 16 bits y el tipo de datos de los miembros de su estructura es de 64 bits, obviamente no cabe en un registro, por lo que habrá que realizar varias operaciones solo para una copia.
  3. la cantidad de registros que tiene tu máquina en realidad: suponiendo que tienes una estructura con un solo miembro, un char (8 bits). Eso debería causar la misma sobrecarga al pasar el parámetro por valor o por referencia (en teoría). Pero existe potencialmente otro peligro. Si su arquitectura tiene registros de datos y direcciones separados, el parámetro pasado por valor ocupará un registro de datos y el parámetro pasado por referencia ocupará un registro de dirección. Pasar el parámetro por valor ejerce presión sobre los registros de datos que generalmente se usan más que los registros de direcciones. Y esto puede causar derrames en la pila.

En pocas palabras, es muy difícil decir cuándo está bien pasar una estructura por valor. Es más seguro simplemente no hacerlo :)


de manera abstracta, un conjunto de valores de datos pasados ​​a una función es una estructura por valor, aunque no declarado como tal. puede declarar una función como una estructura, en algunos casos requiriendo una definición de tipo. cuando haces esto, todo está en la pila. Y ese es el problema. al poner sus valores de datos en la pila, se vuelve vulnerable a la sobre escritura si una función o sub se llama con parámetros antes de utilizar o copiar los datos en otra parte. lo mejor es usar punteros y clases.