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 depila.
¡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);