Comment renvoyer plusieurs valeurs d'une fonction en C?

87

Si j'ai une fonction qui produit un résultat intet un résultat string, comment puis-je les renvoyer tous les deux à partir d'une fonction?

Pour autant que je sache, je ne peux renvoyer qu'une seule chose, comme déterminé par le type précédant le nom de la fonction.

Tony Stark
la source
6
Par stringvoulez-vous dire "J'utilise C ++ et c'est la std::stringclasse" ou "J'utilise C et ceci est un char *pointeur ou un char[]tableau."
Chris Lutz
Eh bien, dans mon cas particulier, il s'agissait de deux entiers: un pour le «score» de ce que je comparais, et un pour l '«index» de l'endroit où ce score maximum a été trouvé. Je voulais utiliser un exemple de chaîne ici juste pour le cas plus général
Tony Stark
Passez la chaîne par référence et rendez l'int. Moyen le plus rapide. Aucune structure requise.
Stefan Steiger
1
Une fonction qui renvoie 2 résultats ne fait-elle pas plus d'une chose? Que dirait l'oncle Bob?
Duncan

Réponses:

123

Je ne sais pas ce que vous stringêtes, mais je vais supposer qu'il gère sa propre mémoire.

Vous avez deux solutions:

1: Renvoyez un structqui contient tous les types dont vous avez besoin.

struct Tuple {
    int a;
    string b;
};

struct Tuple getPair() {
    Tuple r = { 1, getString() };
    return r;
}

void foo() {
    struct Tuple t = getPair();
}

2: Utilisez des pointeurs pour transmettre des valeurs.

void getPair(int* a, string* b) {
    // Check that these are not pointing to NULL
    assert(a);
    assert(b);
    *a = 1;
    *b = getString();
}

void foo() {
    int a, b;
    getPair(&a, &b);
}

Celui que vous choisissez d'utiliser dépend en grande partie de vos préférences personnelles quant à la sémantique que vous préférez.

Travis Gockel
la source
7
Je pense que cela dépend davantage de la relation entre les valeurs de retour. Si l'int est un code d'erreur et que la chaîne est un résultat, ils ne doivent pas être rassemblés dans une structure. C'est juste idiot. Dans ce cas, je retournerais l'int et passerais la chaîne en tant que a char *et a size_tpour la longueur à moins qu'il ne soit absolument vital pour la fonction d'allouer sa propre chaîne et / ou de retourner NULL.
Chris Lutz
@Chris Je suis complètement d'accord avec vous, mais je n'ai aucune idée de la sémantique d'utilisation des variables dont il a besoin.
Travis Gockel
Bon point Chris. Une autre chose que je pense mérite d'être soulignée est la valeur par rapport à la référence. Si je ne me trompe pas, renvoyer la structure comme indiqué dans l'exemple signifie qu'une copie sera faite lors du retour, est-ce correct? (Je suis un peu instable sur C) Alors que l'autre méthode utilise le passage par référence et ne nécessite donc pas d'allouer plus de mémoire. Bien sûr, la structure pourrait être retournée via un pointeur et partager le même avantage, n'est-ce pas? (en veillant à allouer correctement la mémoire et tout cela bien sûr)
RTHarston
@BobVicktor: C n'a pas du tout de sémantique de référence (qui est exclusive à C ++), donc tout est une valeur. La solution à deux pointeurs (# 2) consiste à transmettre des copies des pointeurs dans une fonction, qui déréférenceragetPair ensuite . En fonction de ce que vous faites (OP n'a jamais clarifié s'il s'agit en fait d'une question C), l'allocation peut être un problème, mais ce n'est généralement pas dans le domaine C ++ (l'optimisation de la valeur de retour enregistre tout cela) et dans le domaine C, les données les copies se produisent généralement explicitement (via ou autre). strncpy
Travis Gockel
@TravisGockel Merci pour la correction. Je faisais référence au fait que des pointeurs sont utilisés, donc il ne s'agit pas de copier les valeurs, mais simplement de partager ce qui était déjà alloué. Mais vous avez raison de dire que ce n'est pas correctement appelé pass-by-reference en C. Et merci aussi pour les autres grandes pépites de connaissances. J'adore apprendre ces petites choses sur les langues. :)
RTHarston
10

Option 1: Déclarez une structure avec un int et une chaîne et retournez une variable de structure.

struct foo {    
 int bar1;
 char bar2[MAX];
};

struct foo fun() {
 struct foo fooObj;
 ...
 return fooObj;
}

Option 2: Vous pouvez passer l'un des deux via le pointeur et apporter des modifications au paramètre réel via le pointeur et renvoyer l'autre comme d'habitude:

int fun(char **param) {
 int bar;
 ...
 strcpy(*param,"....");
 return bar;
}

ou

 char* fun(int *param) {
 char *str = /* malloc suitably.*/
 ...
 strcpy(str,"....");
 *param = /* some value */
 return str;
}

Option 3: Similaire à l'option 2. Vous pouvez passer les deux via le pointeur et ne rien renvoyer de la fonction:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}
codaddict
la source
En ce qui concerne l'option 2, vous devez également transmettre la longueur de la chaîne. int fun(char *param, size_t len)
Chris Lutz
Maintenant, cela dépend de ce que fait la fonction. Si nous savons quel type de résultat la fonction fourre dans le tableau char, nous pourrions simplement lui allouer suffisamment d'espace et le passer à la fonction. Pas besoin de passer la longueur. Quelque chose de similaire à la façon dont nous utilisons strcpypar exemple.
codaddict
7

Étant donné que l'un de vos types de résultats est une chaîne (et que vous utilisez C, pas C ++), je recommande de passer des pointeurs en tant que paramètres de sortie. Utilisation:

void foo(int *a, char *s, int size);

et appelez-le comme ceci:

int a;
char *s = (char *)malloc(100); /* I never know how much to allocate :) */
foo(&a, s, 100);

En général, préférez faire l'allocation dans la fonction appelante , pas à l'intérieur de la fonction elle-même, afin que vous puissiez être aussi ouvert que possible pour différentes stratégies d'allocation.

Jesse Beder
la source
6

Deux approches différentes:

  1. Passez vos valeurs de retour par pointeur et modifiez-les dans la fonction. Vous déclarez votre fonction comme nulle, mais elle revient via les valeurs transmises en tant que pointeurs.
  2. Définissez une structure qui regroupe vos valeurs de retour.

Je pense que le n ° 1 est un peu plus évident sur ce qui se passe, bien que cela puisse devenir fastidieux si vous avez trop de valeurs de retour. Dans ce cas, l'option n ° 2 fonctionne assez bien, bien qu'il y ait une surcharge mentale impliquée dans la création de structures spécialisées à cet effet.

James Thompson
la source
1
C n'a pas de références ;-), bien que depuis l'affiche utilisée string, il pourrait être prudent de supposer C ++ ...
Travis Gockel
J'avais totalement oublié ça! J'ai modifié ma réponse pour utiliser des pointeurs, mais je suis clairement dans le pays C ++ depuis trop longtemps. :)
James Thompson
6

Créez un struct et définissez deux valeurs à l'intérieur et retournez la variable struct.

struct result {
    int a;
    char *string;
}

Vous devez allouer de l'espace pour le char *dans votre programme.

zs2020
la source
3

Utilisez des pointeurs comme paramètres de fonction. Ensuite, utilisez-les pour renvoyer plusieurs valeurs.

Bloodlee
la source
2

En passant des paramètres par référence à la fonction.

Exemples:

 void incInt(int *y)
 {
     (*y)++;  // Increase the value of 'x', in main, by one.
 }

Également en utilisant des variables globales, mais ce n'est pas recommandé.

Exemple:

int a=0;

void main(void)
{
    //Anything you want to code.
}
Badr
la source
4
void main(void)Oh comment ça brûle!
Chris Lutz
Que voulez-vous dire? @ Chris Lutz
Badr
6
mainest censé renvoyer un code d'état. Sous * nix, il est plus courant de le déclarer comme int main(int argc, char *argv[]), je crois que Windows a des conventions similaires.
Duncan
2

Une approche consiste à utiliser des macros. Placez-le dans un fichier d'en-têtemultitype.h

#include <stdlib.h>

/* ============================= HELPER MACROS ============================= */

/* __typeof__(V) abbreviation */

#define TOF(V) __typeof__(V)

/* Expand variables list to list of typeof and variable names */

#define TO3(_0,_1,_2,_3) TOF(_0) v0; TOF(_1) v1; TOF(_2) v2; TOF(_3) v3;
#define TO2(_0,_1,_2)    TOF(_0) v0; TOF(_1) v1; TOF(_2) v2;
#define TO1(_0,_1)       TOF(_0) v0; TOF(_1) v1;
#define TO0(_0)          TOF(_0) v0;

#define TO_(_0,_1,_2,_3,TO_MACRO,...) TO_MACRO

#define TO(...) TO_(__VA_ARGS__,TO3,TO2,TO1,TO0)(__VA_ARGS__)

/* Assign to multitype */

#define MTA3(_0,_1,_2,_3) _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2; _3 = mtr.v3;
#define MTA2(_0,_1,_2)    _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2;
#define MTA1(_0,_1)       _0 = mtr.v0; _1 = mtr.v1;
#define MTA0(_0)          _0 = mtr.v0;

#define MTA_(_0,_1,_2,_3,MTA_MACRO,...) MTA_MACRO

#define MTA(...) MTA_(__VA_ARGS__,MTA3,MTA2,MTA1,MTA0)(__VA_ARGS__)

/* Return multitype if multiple arguments, return normally if only one */

#define MTR1(...) {                                                           \
    typedef struct mtr_s {                                                    \
      TO(__VA_ARGS__)                                                         \
    } mtr_t;                                                                  \
    mtr_t *mtr = malloc(sizeof(mtr_t));                                       \
    *mtr = (mtr_t){__VA_ARGS__};                                              \
    return mtr;                                                               \
  }

#define MTR0(_0) return(_0)

#define MTR_(_0,_1,_2,_3,MTR_MACRO,...) MTR_MACRO

/* ============================== API MACROS =============================== */

/* Declare return type before function */

typedef void* multitype;

#define multitype(...) multitype

/* Assign return values to variables */

#define let(...)                                                              \
  for(int mti = 0; !mti;)                                                     \
    for(multitype mt; mti < 2; mti++)                                         \
      if(mti) {                                                               \
        typedef struct mtr_s {                                                \
          TO(__VA_ARGS__)                                                     \
        } mtr_t;                                                              \
        mtr_t mtr = *(mtr_t*)mt;                                              \
        MTA(__VA_ARGS__)                                                      \
        free(mt);                                                             \
      } else                                                                  \
        mt

/* Return */

#define RETURN(...) MTR_(__VA_ARGS__,MTR1,MTR1,MTR1,MTR0)(__VA_ARGS__)

Cela permet de renvoyer jusqu'à quatre variables à partir d'une fonction et de les affecter à jusqu'à quatre variables. À titre d'exemple, vous pouvez les utiliser comme ceci:

multitype (int,float,double) fun() {
    int a = 55;
    float b = 3.9;
    double c = 24.15;

    RETURN (a,b,c);
}

int main(int argc, char *argv[]) {
    int x;
    float y;
    double z;

    let (x,y,z) = fun();

    printf("(%d, %f, %g\n)", x, y, z);

    return 0;
}

Voici ce qu'il imprime:

(55, 3.9, 24.15)

La solution n'est peut-être pas aussi portable car elle nécessite C99 ou une version ultérieure pour les macros variadiques et les déclarations de variables for-statement. Mais je pense que c'était assez intéressant pour poster ici. Un autre problème est que le compilateur ne vous avertira pas si vous leur attribuez des valeurs incorrectes, vous devez donc faire attention.

Des exemples supplémentaires et une version basée sur la pile du code utilisant des unions sont disponibles sur mon référentiel github .

Klas. S
la source
1
Un problème de portabilité plus important est la fonctionnalité non standard__typeof__
MM
Au lieu de typeof, vous pouvez probablement utiliser sizeof. Obtenez la taille des valeurs de retour et allouez un tableau suffisamment grand pour les stocker toutes. Ensuite, vous pouvez le retourner.
Klas. S