retornar - ¿Es seguro devolver una estructura en C o C++?
puede una funcion devolver un struct (11)
Agreguemos una segunda parte a la pregunta: ¿esto varía según el compilador?
De hecho lo hace, como descubrí para mi dolor: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/
Estaba usando gcc en win32 (MinGW) para llamar a las interfaces COM que devolvieron las estructuras. Resulta que MS lo hace de manera diferente a GNU y mi programa (gcc) se estrelló con una pila rota.
Podría ser que MS tenga un terreno más alto aquí, pero lo único que me importa es la compatibilidad ABI entre MS y GNU para construir en Windows.
Si es así, ¿cuál es el comportamiento de las últimas versiones de compiladores para equipos de escritorio: gcc, g ++ y Visual Studio?
Puede encontrar algunos mensajes en una lista de correo de Wine sobre cómo la MS parece hacerlo.
Lo que entiendo es que esto no debería hacerse, pero creo que he visto ejemplos que hacen algo como esto (el código de la nota no es necesariamente sintácticamente correcto, pero la idea está allí)
typedef struct{
int a,b;
}mystruct;
Y luego aquí está una función
mystruct func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
Entendí que siempre debemos devolver un puntero a una estructura malloc''ed si queremos hacer algo como esto, pero estoy seguro de que he visto ejemplos que hacen algo como esto. ¿Es esto correcto? Personalmente, siempre devuelvo un puntero a una estructura malloc''ed o simplemente paso por referencia a la función y modifico los valores allí. (Debido a que entiendo que una vez que el alcance de la función ha terminado, se puede sobrescribir la pila que se utilizó para asignar la estructura).
Agreguemos una segunda parte a la pregunta: ¿esto varía según el compilador? Si es así, ¿cuál es el comportamiento de las últimas versiones de compiladores para equipos de escritorio: gcc, g ++ y Visual Studio?
¿Pensamientos sobre el asunto?
Es perfectamente legal, pero con estructuras grandes hay dos factores que deben tenerse en cuenta: la velocidad y el tamaño de la pila.
Es perfectamente seguro devolver una estructura como lo ha hecho.
Sin embargo, basado en esta afirmación: porque mi entendimiento es que una vez que el alcance de la función termina, cualquier pila que se usó para asignar la estructura puede sobrescribirse, me imagino solo un escenario donde cualquiera de los miembros de la estructura se asignó dinámicamente ( malloc''ed o new''ed), en cuyo caso, sin RVO, los miembros dinámicamente asignados se destruirán y la copia devuelta tendrá un miembro que señale a la basura.
Es perfectamente seguro, y no está mal hacerlo. Además: no varía según el compilador.
Generalmente, cuando (como su ejemplo) su estructura no es demasiado grande, yo diría que este enfoque es incluso mejor que devolver una estructura malloc
(el malloc
es una operación costosa).
Es perfectamente seguro.
Estás regresando por valor. Lo que llevaría a un comportamiento indefinido es si regresas por referencia.
//safe
mystruct func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
//undefined behavior
mystruct& func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
El comportamiento de su fragmento es perfectamente válido y definido. No varía según el compilador. ¡Está bien!
Personalmente, siempre devuelvo un puntero a una estructura malloc''ed
No deberias. Debe evitar la memoria asignada dinámicamente cuando sea posible.
o simplemente haga un pase por referencia a la función y modifique los valores allí.
Esta opción es perfectamente válida. Es una cuestión de elección. En general, puede hacer esto si desea devolver algo más desde la función, mientras modifica la estructura original.
Porque mi entendimiento es que una vez que el alcance de la función termina, cualquier pila que se usó para asignar la estructura se puede sobrescribir
Esto está mal. Quiero decir, es un poco correcto, pero devuelves una copia de la estructura que creaste dentro de la función. Teóricamente En la práctica, RVO puede y probablemente ocurrirá. Lea sobre la optimización del valor de retorno. Esto significa que aunque retval
parece estar fuera del alcance cuando la función finaliza, en realidad podría construirse en el contexto de llamada, para evitar la copia adicional. Esta es una optimización que el compilador es libre de implementar.
La seguridad depende de cómo se implementó la estructura en sí. Acabo de tropezar con esta pregunta al implementar algo similar, y aquí está el problema potencial.
El compilador, al devolver el valor realiza algunas operaciones (entre otras posibles):
- Llama al constructor de copias
mystruct(const mystruct&)
(this
es una variable temporal fuera de la funciónfunc
asignada por el compilador) - llama al destructor
~mystruct
sobre la variable que se asignó dentro defunc
- llama a
mystruct::operator=
si el valor devuelto está asignado a otra cosa con=
- llama al destructor
~mystruct
sobre la variable temporal utilizada por el compilador
Ahora, si mystruct
es tan simple como el descrito aquí, todo está bien, pero si tiene un puntero (como char*
) o una administración de memoria más complicada, todo depende de cómo mystruct::operator=
, mystruct(const mystruct&)
, y ~mystruct
están implementados. Por lo tanto, sugiero precauciones al devolver estructuras de datos complejas como valor.
La vida útil del objeto mystruct
en tu función termina cuando sales de la función. Sin embargo, está pasando el objeto por valor en la declaración de devolución. Esto significa que el objeto se copia de la función a la función de llamada. El objeto original se ha ido, pero la copia vive.
No es seguro devolver una estructura. Me encanta hacerlo yo mismo, pero si alguien agregará un constructor de copia a la estructura devuelta más tarde, se llamará al constructor de copia. Esto podría ser inesperado y puede romper el código. Este error es muy difícil de encontrar.
Tenía una respuesta más elaborada, pero al moderador no le gustó. Entonces, a su juicio, mi consejo es corto.
No solo es seguro devolver una struct
en C (o una class
en C ++, donde struct
-s son realmente class
-es con public:
predeterminado public:
miembros), pero una gran cantidad de software lo está haciendo.
Por supuesto, al devolver una class
en C ++, el lenguaje especifica que se llamará a algún destructor o constructor en movimiento, pero hay muchos casos en que el compilador podría optimizarlo.
Además, el Linux x86-64 ABI especifica que devolver una struct
con dos valores escalares (por ejemplo, punteros o long
) se realiza a través de registros ( %rax
y %rdx
), por lo que es muy rápido y eficiente. Entonces, para ese caso particular, probablemente sea más rápido devolver una struct
campos de dos escalares que hacer cualquier otra cosa (por ejemplo, almacenarlas en un puntero pasado como argumento).
Devolver una struct
campo de dos struct
es mucho más rápido que malloc
-ing y devolver un puntero.
También estaré de acuerdo con sftrabbit, Life de hecho termina y el área de la pila se aclara, pero el compilador es lo suficientemente inteligente como para garantizar que todos los datos se recuperen en los registros o de alguna otra manera.
Un ejemplo simple para la confirmación se da a continuación. (Tomado del ensamblado del compilador Mingw)
_func:
push ebp
mov ebp, esp
sub esp, 16
mov eax, DWORD PTR [ebp+8]
mov DWORD PTR [ebp-8], eax
mov eax, DWORD PTR [ebp+12]
mov DWORD PTR [ebp-4], eax
mov eax, DWORD PTR [ebp-8]
mov edx, DWORD PTR [ebp-4]
leave
ret
Puede ver que el valor de b se ha transmitido a través de edx. mientras que el eax predeterminado contiene valor para a.
Un tipo de estructura puede ser el tipo para el valor devuelto por una función. Es seguro porque el compilador creará una copia de struct y devolverá la copia, no la estructura local de la función.
typedef struct{
int a,b;
}mystruct;
mystruct func(int c, int d){
mystruct retval;
cout << "func:" <<&retval<< endl;
retval.a = c;
retval.b = d;
return retval;
}
int main()
{
cout << "main:" <<&(func(1,2))<< endl;
system("pause");
}