87 votes

Est-il sûr de retourner un struct en C ou C++ ?

Ce que je comprends, c'est que cela ne devrait pas être fait, mais je crois avoir vu des exemples qui font quelque chose comme ceci (notez que le code 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 devions toujours renvoyer un pointeur vers une structure malloc'ed si nous voulons faire quelque chose comme ça, mais je suis sûr d'avoir vu des exemples qui font quelque chose comme ça. Est-ce correct ? Personnellement, je renvoie toujours un pointeur vers une structure mallocée ou je passe simplement par référence à la fonction et je modifie les valeurs à cet endroit. (Parce que si je comprends bien, une fois que la portée de la fonction est terminée, la 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 c'est le cas, quel est le comportement des dernières versions des compilateurs pour ordinateurs de bureau : gcc, g++ et Visual Studio ?

Des idées sur la question ?

35 votes

"Ce que je comprends, c'est que ça ne devrait pas être fait", dit qui ? Je le fais tout le temps. Notez également que le typedef n'est pas nécessaire en C++, et qu'il n'existe pas de "C/C++".

4 votes

La question semble être pas être ciblé sur le c++.

5 votes

@PlasmaHH Copier de grandes structures peut être inefficace. C'est pourquoi il faut être prudent et bien réfléchir avant de retourner une structure par valeur, surtout si la structure a un constructeur de copie coûteux et que le compilateur n'est pas bon pour optimiser la valeur de retour. J'ai récemment optimisé une application qui passait une grande partie de son temps dans des constructeurs de copie pour quelques grandes structures qu'un programmeur avait décidé de retourner par valeur partout. Cette inefficacité nous coûtait environ 800 000 dollars en matériel de centre de données supplémentaire que nous devions acheter.

86voto

Pablo Santa Cruz Points 73944

C'est parfaitement sûr, et ce n'est pas mal de le faire. De plus, cela ne varie pas selon le compilateur.

En général, lorsque (comme dans votre exemple) votre structure n'est pas trop grande, je dirais que cette approche est encore meilleure que de retourner une structure mallocée ( malloc est une opération coûteuse).

3 votes

Serait-il encore sûr si l'un des champs était un char* ? Il y aurait alors un pointeur dans la structure

0 votes

Oui, c'est vrai. Faites juste attention quand vous malloc/librez que char* .

3 votes

@user963258 en fait, cela dépend de la façon dont vous implémentez la copie du constructeur et du destructeur.

75voto

Luchian Grigore Points 136646

C'est parfaitement sûr.

Vous rentrez par valeur. Ce qui conduirait à un comportement indéfini, c'est si vous retourniez 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. Il ne varie pas selon le compilateur. C'est bon !

Personnellement, je renvoie toujours un pointeur vers une structure mallocée.

Tu ne devrais pas. Vous devez éviter la mémoire allouée dynamiquement lorsque cela est possible.

ou simplement faire un passage par référence à la fonction et modifier les valeurs là.

Cette option est parfaitement valable. Il s'agit d'une question de choix. En général, vous le faites si vous voulez retourner quelque chose d'autre de la fonction, tout en modifiant la structure originale.

Parce que si je comprends bien, une fois que le champ d'application de la fonction est fonction, la pile qui a été utilisée pour allouer la structure peut être écrasée

C'est faux. Je veux dire, c'est en quelque sorte correct, mais vous retournez une copie de la structure que vous créez dans la fonction. Théoriquement . En pratique, RVO peut se produire et se produira probablement. Renseignez-vous sur l'optimisation des valeurs de retour. Cela signifie que même si retval semble sortir de la portée lorsque la fonction se termine, il pourrait en fait être construit dans le contexte d'appel, afin d'éviter la copie supplémentaire. Il s'agit d'une optimisation que le compilateur est libre d'implémenter.

5 votes

+1 pour avoir mentionné le RVO. Cette optimisation importante rend en fait ce modèle réalisable pour les objets avec des constructeurs de copie coûteux, comme les conteneurs STL.

1 votes

Il convient de mentionner que, bien que le compilateur soit libre d'optimiser les valeurs de retour, il n'y a aucune garantie qu'il le fasse. Ce n'est pas quelque chose sur lequel vous pouvez compter, seulement espérer.

1 votes

-1 pour "éviter la mémoire allouée dynamiquement lorsque cela est possible". Cela tend à être une règle de newb et résulte fréquemment en un code où LARGE des quantités de données sont renvoyées (et ils se demandent pourquoi les choses tournent lentement) alors qu'un simple pointeur peut faire gagner beaucoup de temps. Le site correct La règle consiste à renvoyer des structures ou des pointeurs en fonction de la rapidité, de l'utilisation et de l'efficacité. clarté .

12voto

Joseph Mansfield Points 59346

La durée de vie de la mystruct dans votre fonction se termine effectivement lorsque vous quittez la fonction. Cependant, vous transmettez l'objet par valeur dans l'instruction de retour. Cela signifie que l'objet est copié de la fonction vers la fonction appelante. L'objet original a disparu, mais la copie continue à vivre.

5voto

ebutusov Points 417

C'est parfaitement légal, mais avec les grands structs, deux facteurs doivent être pris en considération : la vitesse et la taille de la pile.

2 votes

Vous avez entendu parler de l'optimisation des valeurs de retour ?

0 votes

Oui, mais nous parlons en général du retour de struct par valeur, et il y a des cas où le compilateur est incapable d'effectuer la RVO.

4 votes

Je dirais que vous ne devez vous préoccuper de la copie supplémentaire qu'après avoir établi un profil.

4voto

haberdar Points 381

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 la structure et retourner la copie et non 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");
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X