Ce que je comprends, c’est que cela ne devrait pas être fait, mais je pense avoir déjà vu des exemples qui font quelque chose comme cela (le code de la note n’est pas nécessairement syntaxiquement correct, mais l’idée est là)
typedef struct{
int a,b;
}mystruct;
Et puis voici une fonction
mystruct func(int c, int d){
mystruct retval;
retval.a = c;
retval.b = d;
return retval;
}
J'ai compris que nous devrions toujours renvoyer un pointeur sur une structure mallocée si nous voulons faire quelque chose comme ça, mais je suis certain que j'ai vu des exemples qui font quelque chose comme ça. Est-ce correct? Personnellement, je retourne toujours un pointeur sur une structure malloc 'ou bien je fais juste un passage en référence à la fonction pour y modifier les valeurs. (Parce que ma compréhension est qu'une fois la portée de la fonction terminée, toute pile utilisée pour allouer la structure peut être écrasée).
Ajoutons une deuxième partie à la question: Cela varie-t-il selon le compilateur? Si tel est le cas, quel est le comportement des dernières versions des compilateurs pour ordinateurs de bureau: gcc, g ++ et Visual Studio?
Des pensées sur le sujet?
C'est parfaitement sûr et ce n'est pas mal de le faire. En outre: cela ne varie pas en fonction du compilateur.
Habituellement, lorsque (comme dans votre exemple) votre structure n'est pas trop grosse, je dirais que cette approche est encore meilleure que de renvoyer une structure mallocée (malloc
est une opération coûteuse).
C'est parfaitement en sécurité.
Vous revenez par valeur. Ce qui conduirait à un comportement indéfini est si vous reveniez par référence.
//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;
}
Le comportement de votre extrait est parfaitement valide et défini. Cela ne varie pas en fonction du compilateur. ça va!
Personnellement, je retourne toujours un pointeur sur une structure malloc
Tu ne devrais pas. Vous devez éviter autant que possible la mémoire allouée dynamiquement.
ou faites simplement un passage en référence à la fonction et modifiez les valeurs.
Cette option est parfaitement valide. C'est une question de choix. En général, vous faites cela si vous voulez renvoyer quelque chose d'autre de la fonction, tout en modifiant la structure d'origine.
Parce que ma compréhension est qu'une fois la portée de la fonction terminée, toute pile utilisée pour allouer la structure peut être écrasée
C'est faux. Je voulais dire, c'est en quelque sorte correct, mais vous renvoyez une copie de la structure que vous créez dans la fonction. Théoriquement. En pratique, RVO peut et va probablement se produire. Lisez sur l'optimisation de la valeur de retour. Cela signifie que bien que retval
semble sortir de sa portée lorsque la fonction se termine, il se peut que celle-ci soit construite dans le contexte de l'appelant, afin d'empêcher la copie supplémentaire. C'est une optimisation que le compilateur est libre d'implémenter.
La durée de vie de l'objet mystruct
de votre fonction prend effectivement fin lorsque vous quittez la fonction. Cependant, vous transmettez l'objet par valeur dans l'instruction return. Cela signifie que l'objet est copié hors de la fonction dans la fonction appelante. L'objet d'origine a disparu, mais la copie continue.
Non seulement il est prudent de retourner un struct
en C (ou un class
en C++, où struct
-s sont réellement class
- es avec la valeur par défaut public:
_ membres), mais de nombreux logiciels le font.
Bien sûr, lors du renvoi de class
en C++, le langage spécifie que certains destructeurs ou constructeurs mobiles seraient appelés, mais il existe de nombreux cas dans lesquels cela pourrait être optimisé par le compilateur.
De plus, Linux x86-64 ABI spécifie que renvoyer un struct
avec deux scalaires (par exemple des pointeurs , ou long
) est effectuée via les registres (%rax
& %rdx
) est donc très rapide et efficace. Ainsi, dans ce cas particulier, il est probablement plus rapide de renvoyer un tel champ à deux scalaires struct
que de le faire autrement (par exemple, en les stockant dans un pointeur passé en argument).
Retourner un tel champ à deux scalaires struct
est alors beaucoup plus rapide que malloc
- et renvoyer un pointeur.
C'est parfaitement légal, mais avec de grandes structures, il faut tenir compte de deux facteurs: la vitesse et la taille de la pile.
Un type de structure peut être le type de la valeur renvoyée par une fonction. C'est sûr car le compilateur va créer une copie de struct et renvoyer la copie, pas la structure locale dans la fonction.
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");
}
Il est parfaitement sûr de retourner une structure comme vous l’avez fait.
Cependant, sur la base de cette déclaration: car je crois comprendre qu'une fois la portée de la fonction terminée, la pile utilisée pour allouer la structure peut être remplacée, J'imagine seulement un scénario dans lequel l'un des membres de la structure a été allouée dynamiquement (malloced ou new'ed), auquel cas, sans RVO, les membres alloués dynamiquement seront détruits et la copie renvoyée aura un membre pointant vers la poubelle.
La sécurité dépend de la manière dont la structure elle-même a été implémentée. Je suis tombé par hasard sur cette question tout en mettant en œuvre quelque chose de similaire, et voici le problème potentiel.
Le compilateur, lorsqu’il renvoie la valeur, effectue quelques opérations (parmi d’autres éventuellement):
mystruct(const mystruct&)
(this
est une variable temporaire extérieure la fonction func
allouée par le compilateur lui-même)~mystruct
sur la variable allouée à l'intérieur de func
mystruct::operator=
si la valeur renvoyée est affectée à autre chose avec =
~mystruct
sur la variable temporaire utilisée par le compilateurMaintenant, si mystruct
est aussi simple que ce qui est décrit ici, tout va bien, mais si elle possède un pointeur (comme char*
) Ou une gestion de mémoire plus complexe, tout dépend de la façon dont mystruct::operator=
, mystruct(const mystruct&)
et ~mystruct
sont implémentés. Par conséquent, je suggère des mises en garde lors du renvoi de structures de données complexes sous forme de valeur.
Je serai également d’accord avec sftrabbit, Life finit effectivement et la zone de pile s’éclaircit, mais le compilateur est suffisamment intelligent pour garantir que toutes les données soient récupérées dans des registres ou d’une autre manière.
Un exemple simple de confirmation est donné ci-dessous (tiré de l’assemblage du compilateur 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
Vous pouvez voir que la valeur de b a été transmise par edx. tandis que le eax par défaut contient une valeur pour a.
Ajoutons une deuxième partie à la question: Cela varie-t-il selon le compilateur?
En effet, comme je l'ai découvert à mes dépens: http://sourceforge.net/p/mingw-w64/mailman/message/33176880/
J'utilisais gcc sur win32 (MinGW) pour appeler des interfaces COM qui renvoyaient des structures. Il s'avère que MS le fait différemment à GNU et ainsi mon programme (gcc) s'est écrasé avec une pile cassée.
Il se pourrait que MS ait le meilleur terrain ici - mais tout ce qui me préoccupe est la compatibilité ABI entre MS et GNU pour la construction sous Windows.
Si tel est le cas, quel est le comportement des dernières versions des compilateurs pour ordinateurs de bureau: gcc, g ++ et Visual Studio
Vous trouverez sur la liste de diffusion de Wine des messages sur la façon dont MS semble le faire.
Il n'est pas prudent de renvoyer une structure. J'aime le faire moi-même, mais si quelqu'un ajoute un constructeur de copie à la structure renvoyée plus tard, le constructeur de copie sera appelé. Cela peut être inattendu et peut casser le code. Ce bug est très difficile à trouver.
J'avais une réponse plus élaborée, mais le modérateur ne l'aimait pas. Donc, à votre charge, mon conseil est court.