web-dev-qa-db-fra.com

Est-il prudent de retourner une structure en C ou C ++?

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?

78
jzepeda

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).

72
Pablo Santa Cruz

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.

68
Luchian Grigore

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.

9
Joseph Mansfield

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.

7

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.

5
ebutusov

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");
}
4
haberdar

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.

4
maress

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):

  1. Appelle le constructeur de copie mystruct(const mystruct&) (this est une variable temporaire extérieure la fonction func allouée par le compilateur lui-même)
  2. appelle le destructeur ~mystruct sur la variable allouée à l'intérieur de func
  3. appelle mystruct::operator= si la valeur renvoyée est affectée à autre chose avec =
  4. appelle le destructeur ~mystruct sur la variable temporaire utilisée par le compilateur

Maintenant, 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.

4
Filippo

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.

3
perilbrain

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.

2
effbiae

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.

2
Igor Polk