una todas llamar librerias lenguaje las funciones funcion ejemplos como codigos basicos c compiler-theory compiler-optimization

todas - librerias de c++



¿Cómo los compiladores de C implementan funciones que devuelven estructuras grandes? (5)

El valor de retorno de una función generalmente se almacena en la pila o en un registro. Pero para una estructura grande, tiene que estar en la pila. ¿Cuánta copia tiene que ocurrir en un compilador real para este código? ¿O está optimizado lejos?

Por ejemplo:

struct Data { unsigned values[256]; }; Data createData() { Data data; // initialize data values... return data; }

(Suponiendo que la función no puede estar en línea ...)


Pero para una estructura grande, tiene que estar en el montón de pila.

¡Así es! Una gran estructura declarada como variable local se asigna en la pila. Me alegra haber aclarado eso.

En cuanto a evitar copiar, como han dicho otros:

  • La mayoría de las convenciones de llamada tratan con la "estructura que devuelve la función" al pasar un parámetro adicional que señala la ubicación en el marco de la pila de la persona que llama en el que se debe colocar la estructura. Esto es definitivamente un asunto de la convención de la llamada y no del idioma.

  • Con esta convención de llamadas, es posible que incluso un compilador relativamente simple se dé cuenta cuando una ruta de código definitivamente va a devolver una estructura, y para que corrija las asignaciones a los miembros de esa estructura de modo que vayan directamente al marco del llamante y no lo hagan. t tiene que ser copiado La clave es que el compilador observe que todas las rutas de código de terminación a través de la función devuelven la misma variable de estructura. Si ese es el caso, el compilador puede usar de forma segura el espacio en el marco de la persona que llama, eliminando la necesidad de una copia en el punto de retorno.


Hay muchos ejemplos dados, pero básicamente

Esta pregunta no tiene una respuesta definitiva. Dependerá del compilador.

C no especifica cómo se devuelven las estructuras grandes desde una función.

Aquí hay algunas pruebas para un compilador en particular, gcc 4.1.2 en x86 RHEL 5.4

Caso trivial gcc, sin copia

[00:05:21 1 ~] $ gcc -O2 -S -c t.c [00:05:23 1 ~] $ cat t.s .file "t.c" .text .p2align 4,,15 .globl createData .type createData, @function createData: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl $1, 24(%eax) popl %ebp ret $4 .size createData, .-createData .ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)" .section .note.GNU-stack,"",@progbits

caso más realista de gcc, asignar en la pila, memcpy al llamante

#include <stdlib.h> struct Data { unsigned values[256]; }; struct Data createData() { struct Data data; int i; for(i = 0; i < 256 ; i++) data.values[i] = rand(); return data; } [00:06:08 1 ~] $ gcc -O2 -S -c t.c [00:06:10 1 ~] $ cat t.s .file "t.c" .text .p2align 4,,15 .globl createData .type createData, @function createData: pushl %ebp movl %esp, %ebp pushl %edi pushl %esi pushl %ebx movl $1, %ebx subl $1036, %esp movl 8(%ebp), %edi leal -1036(%ebp), %esi .p2align 4,,7 .L2: call rand movl %eax, -4(%esi,%ebx,4) addl $1, %ebx cmpl $257, %ebx jne .L2 movl %esi, 4(%esp) movl %edi, (%esp) movl $1024, 8(%esp) call memcpy addl $1036, %esp movl %edi, %eax popl %ebx popl %esi popl %edi popl %ebp ret $4 .size createData, .-createData .ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)" .section .note.GNU-stack,"",@progbits

gcc 4.4.2 ### ha crecido mucho y no se copia para el caso no trivial anterior.

.file "t.c" .text .p2align 4,,15 .globl createData .type createData, @function createData: pushl %ebp movl %esp, %ebp pushl %edi pushl %esi pushl %ebx movl $1, %ebx subl $1036, %esp movl 8(%ebp), %edi leal -1036(%ebp), %esi .p2align 4,,7 .L2: call rand movl %eax, -4(%esi,%ebx,4) addl $1, %ebx cmpl $257, %ebx jne .L2 movl %esi, 4(%esp) movl %edi, (%esp) movl $1024, 8(%esp) call memcpy addl $1036, %esp movl %edi, %eax popl %ebx popl %esi popl %edi popl %ebp ret $4 .size createData, .-createData .ident "GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)" .section .note.GNU-stack,"",@progbits

Además, VS2008 (compilado lo anterior como C) reservará los datos de la estructura en la pila de createData () y realizará un bucle de rep movsd para volver a copiarlo al rep movsd de la llamada en el modo de depuración, en el modo de liberación moverá el valor de retorno del rand () (% eax) directamente a la persona que llama


Ninguna; No se hacen copias.

La dirección del valor de retorno de datos de la persona que llama se pasa realmente como un argumento oculto a la función, y la función createData simplemente escribe en el marco de la pila de la persona que llama.

Esto se conoce como la optimización del valor de retorno nombrado . También vea las preguntas frecuentes de c ++ sobre este tema .

Los compiladores de C ++ de calidad comercial implementan el retorno por valor de una manera que les permite eliminar la sobrecarga, al menos en casos simples

...

Cuando yourCode () llama a rbv (), el compilador pasa en secreto un puntero a la ubicación donde se supone que rbv () construye el objeto "devuelto".

Puede demostrar que esto se ha hecho agregando un destructor con un printf a su estructura. El destructor solo debe llamarse una vez si esta optimización de retorno por valor está en funcionamiento, de lo contrario dos veces.

También puedes revisar el montaje para ver que esto suceda:

Data createData() { Data data; // initialize data values... data.values[5] = 6; return data; }

Aquí está la asamblea:

__Z10createDatav: LFB2: pushl %ebp LCFI0: movl %esp, %ebp LCFI1: subl $1032, %esp LCFI2: movl 8(%ebp), %eax movl $6, 20(%eax) leave ret $4 LFE2:

Curiosamente, asignó suficiente espacio en la pila para el elemento de datos subl $1032, %esp , pero tenga en cuenta que toma el primer argumento en la pila 8(%ebp) como la dirección base del objeto, y luego inicializa el elemento 6 de esa pila. ít. Ya que no especificamos ningún argumento para createData, esto es curioso hasta que te das cuenta de que es el puntero secreto oculto de la versión de datos del padre.


gcc en linux emitirá un memcpy () para copiar la estructura nuevamente en la pila de la persona que llama. Si la función tiene un enlace interno, hay más optimizaciones disponibles.


typedef struct { unsigned value[256]; } Data; Data createData(void) { Data r; calcualte(&r); return r; } Data d = createData();

msvc (6,8,9) y gcc mingw (3.4.5,4.4.0) generará código como el siguiente pseudocódigo

void createData(Data* r) { calculate(&r) } Data d; createData(&d);