web-dev-qa-db-fra.com

Retourne une `struct` d'une fonction en C

Aujourd’hui, j’enseignais à un couple d’amis comment utiliser C structs. L'un d'eux a demandé si vous pouviez renvoyer une struct d'une fonction à laquelle j'ai répondu: "Non! Vous renverriez des pointeurs dynamiquement à malloced structs."

Venant de quelqu'un qui utilise principalement le C++, je m'attendais à ne pas pouvoir renvoyer structs par valeurs. En C++, vous pouvez surcharger le operator = pour vos objets et il est parfaitement logique d’avoir une fonction pour renvoyer votre objet par valeur. En C, cependant, vous n’avez pas cette option et cela m’a amené à réfléchir à ce que fait réellement le compilateur. Considérer ce qui suit:

struct MyObj{
    double x, y;
};

struct MyObj foo(){
    struct MyObj a;

    a.x = 10;
    a.y = 10;

    return a;
}        

int main () {

    struct MyObj a;

    a = foo();    // This DOES work
    struct b = a; // This does not work

    return 0;
}    

Je comprends pourquoi struct b = a; ne devrait pas fonctionner - vous ne pouvez pas surcharger operator = pour votre type de données. Comment se fait-il que a = foo(); compile bien? Est-ce que cela signifie autre chose que struct b = a;? La question à poser est peut-être la suivante: que fait exactement l'instruction return associée au signe =?

[modifier]: Ok, je viens de me faire remarquer que struct b = a est une erreur de syntaxe - c'est exact et je suis un idiot! Mais cela rend encore plus compliqué! Utiliser struct MyObj b = a fonctionne vraiment! Qu'est-ce que j'oublie ici?

135
GradGuy

Vous pouvez renvoyer une structure depuis une fonction (ou utiliser l'opérateur =) sans aucun problème. C'est une partie bien définie du langage. Le seul problème avec struct b = a est que vous n'avez pas fourni de type complet. struct MyObj b = a fonctionnera parfaitement. Vous pouvez également transmettre des structures à fonctions - une structure est identique à tout type intégré aux fins de la transmission de paramètres, des valeurs renvoyées et de l'affectation.

Voici un programme de démonstration simple qui effectue les trois opérations - passe une structure en tant que paramètre, retourne une structure à partir d'une fonction et utilise des structures dans des instructions d'affectation:

#include <stdio.h>

struct a {
   int i;
};

struct a f(struct a x)
{
   struct a r = x;
   return r;
}

int main(void)
{
   struct a x = { 12 };
   struct a y = f(x);
   printf("%d\n", y.i);
   return 0;
}

L'exemple suivant est à peu près identique, mais utilise le type intégré int à des fins de démonstration. Les deux programmes ont le même comportement en ce qui concerne le passage par valeur pour le passage de paramètres, l’affectation, etc.:

#include <stdio.h>

int f(int x) 
{
  int r = x;
  return r;
}

int main(void)
{
  int x = 12;
  int y = f(x);
  printf("%d\n", y);
  return 0;
}
169
Carl Norum

Lors d'un appel tel que a = foo();, le compilateur peut pousser le address de la structure de résultat sur la pile et le transmettre en tant que pointeur "masqué" à la fonction foo(). Effectivement, cela pourrait devenir quelque chose comme:

void foo(MyObj *r) {
    struct MyObj a;
    // ...
    *r = a;
}

foo(&a);

Cependant, sa mise en œuvre dépend du compilateur et/ou de la plate-forme. Comme le note Carl Norum, si la structure est suffisamment petite, elle peut même être renvoyée complètement dans un registre.

27
Greg Hewgill

La ligne struct b ne fonctionne pas car c'est une erreur de syntaxe. Si vous développez pour inclure le type cela fonctionnera très bien

struct MyObj b = a;  // Runs fine

Ce que C fait ici est essentiellement une memcpy de la structure source à la destination. Ceci est vrai à la fois pour l'affectation et le retour de valeurs struct (et en réalité toutes les autres valeurs en C)

12
JaredPar

oui, il est possible que nous puissions passer de la structure à la structure de retour. Vous aviez raison mais vous n'avez pas passé le type de données qui devrait ressembler à cette structure MyObj b = a. 

En fait, j’ai aussi découvert que j’essayais de trouver une meilleure solution pour renvoyer plus d’une valeur pour fonction sans utiliser de pointeur ou de variable globale.

Ci-dessous, l'exemple pour la même chose, qui calcule l'écart des notes d'un élève par rapport à la moyenne.

#include<stdio.h>
struct marks{
    int maths;
    int physics;
    int chem;
};

struct marks deviation(struct marks student1 , struct marks student2 );

int main(){

    struct marks student;
    student.maths= 87;
    student.chem = 67;
    student.physics=96;

    struct marks avg;
    avg.maths= 55;
    avg.chem = 45;
    avg.physics=34;
    //struct marks dev;
    struct marks dev= deviation(student, avg );
    printf("%d %d %d" ,dev.maths,dev.chem,dev.physics);

    return 0;
 }

struct marks deviation(struct marks student , struct marks student2 ){
    struct marks dev;

    dev.maths = student.maths-student2.maths;
    dev.chem = student.chem-student2.chem;
    dev.physics = student.physics-student2.physics; 

    return dev;
}
6
Aman

Vouspouvezassigner des structures dans C. a = b; est une syntaxe valide.

Vous avez simplement laissé de côté une partie du type - la balise struct - dans votre ligne qui ne fonctionne pas.

3
DigitalRoss

Autant que je me souvienne, les premières versions de C permettaient uniquement de renvoyer une valeur que Pouvait tenir dans un registre de processeur, ce qui signifie que vous ne pouviez renvoyer un pointeur à. La même restriction s'appliquait aux arguments de fonction.

Des versions plus récentes permettent de faire circuler des objets de données plus volumineux tels que structs ... Je pense que cette fonctionnalité était déjà courante dans les années quatre-vingt ou au début des années quatre-vingt-dix.

Les tableaux, cependant, peuvent toujours être passés et retournés uniquement en tant que pointeurs.

3
Giorgio

Il n'y a aucun problème à renvoyer une structure. Il sera passé par valeur

Mais que se passe-t-il si la structure contient un membre ayant l'adresse d'une variable locale?

struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

Maintenant, ici, e1.name contient une adresse mémoire locale de la fonction get () . Une fois que get () est retourné, l’adresse locale du nom aurait été libérée .. accéder à cette adresse, cela peut provoquer une erreur de segmentation, car nous essayons une adresse libérée. C'est mauvais..

Où e1.id sera parfaitement valide puisque sa valeur sera copiée dans e2.id

Donc, nous devrions toujours essayer d'éviter de renvoyer les adresses de mémoire locale d'une fonction.

Tout ce qui est mallocé peut être retourné comme et quand voulu

2
Jagan

struct var e2 adresse poussé comme argument pour appeler la pile et les valeurs y sont assignées. En fait, get () retourne l'adresse de e2 dans eax reg. Cela fonctionne comme un appel par référence.

1
Bala
struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

fonctionne bien avec les versions les plus récentes des compilateurs .

0
saroj panda