¿Cómo devuelvo varios valores de una función en C?

87

Si tengo una función que produce un resultado inty un resultado string, ¿cómo devuelvo ambos de una función?

Por lo que puedo decir, solo puedo devolver una cosa, según lo determinado por el tipo que precede al nombre de la función.

Tony Stark
fuente
6
Por stringQué quiere decir "Estoy usando C ++ y esta es la std::stringclase" o "Estoy usando C y este es un char *puntero o char[]matriz."
Chris Lutz
bueno, en mi caso particular, eran dos entradas: una para la 'puntuación' de lo que estaba comparando, y otra para el 'índice' de dónde se encontró esa puntuación máxima. Quería usar un ejemplo de cadena aquí solo para el caso más general
Tony Stark
Pase la cadena por referencia y devuelva el int. La manera más rápida. No se requieren estructuras.
Stefan Steiger
1
¿No es una función que devuelve 2 resultados haciendo más de una cosa? ¿Qué diría el tío Bob?
Duncan

Respuestas:

123

No sé cuál stringes el tuyo , pero voy a asumir que maneja su propia memoria.

Tienes dos soluciones:

1: Devuelve un structque contiene todos los tipos que necesitas.

struct Tuple {
    int a;
    string b;
};

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

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

2: Utilice punteros para pasar valores.

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);
}

El que elija utilizar depende en gran medida de las preferencias personales en cuanto a la semántica que más le guste.

Travis Gockel
fuente
7
Creo que depende más de qué tan relacionados estén los valores de retorno. Si el int es un código de error y la cadena es un resultado, entonces estos no deben juntarse en una estructura. Eso es una tontería. En ese caso, devolvería el int y pasaría la cadena como a char *y a size_tpara la longitud a menos que sea absolutamente vital que la función asigne su propia cadena y / o regrese NULL.
Chris Lutz
@Chris Estoy completamente de acuerdo contigo, pero no tengo idea de la semántica de uso de las variables que necesita.
Travis Gockel
Buen punto Chris. Otra cosa que creo que vale la pena señalar es el valor frente a la referencia. Si no me equivoco, devolver la estructura como se muestra en el ejemplo significa que se realizará una copia al devolverlo, ¿es correcto? (Estoy un poco tembloroso en C) Mientras que el otro método usa paso por referencia y, por lo tanto, no requiere asignar más memoria. Por supuesto, la estructura podría devolverse a través de un puntero y compartir ese mismo beneficio, ¿verdad? (asegurándose de asignar la memoria correctamente y todo eso, por supuesto)
RTHarston
@BobVicktor: C no tiene semántica de referencia en absoluto (eso es exclusivo de C ++), por lo que todo es un valor. La solución de dos punteros (# 2) es pasar copias de los punteros a una función, que getPairluego elimina las referencias . Dependiendo de lo que esté haciendo (OP nunca aclaró si esto es realmente una pregunta C), la asignación puede ser una preocupación, pero generalmente no está en la tierra C ++ (la optimización del valor de retorno guarda todo esto) y en la tierra C, los datos las copias suelen suceder de forma explícita (vía strncpyo lo que sea).
Travis Gockel
@TravisGockel Gracias por la corrección. Me refería al hecho de que se usan punteros, por lo que no se están copiando los valores, solo se comparte lo que ya estaba asignado. Pero tiene razón al decir que eso no se llama correctamente paso por referencia en C. Y gracias también por las otras grandes pepitas de conocimiento. Me encanta aprender estas pequeñas cosas sobre idiomas. :)
RTHarston
10

Option 1: Declara una estructura con un int y una cadena y devuelve una variable de estructura.

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

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

Option 2: Puede pasar uno de los dos a través del puntero y realizar cambios en el parámetro real a través del puntero y devolver el otro como de costumbre:

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

o

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

Option 3: Similar a la opción 2. Puede pasar ambos a través del puntero y no devolver nada de la función:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}
codaddict
fuente
Con respecto a la opción 2, también debe pasar la longitud de la cadena. int fun(char *param, size_t len)
Chris Lutz
Eso depende de lo que esté haciendo la función. Si sabemos qué tipo de resultado introduce la función en la matriz de caracteres, podríamos asignarle suficiente espacio y pasarlo a la función. No es necesario pasar el largo. Algo parecido a como usamos strcpypor ejemplo.
codaddict
7

Dado que uno de sus tipos de resultado es una cadena (y está usando C, no C ++), le recomiendo pasar punteros como parámetros de salida. Utilizar:

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

y llámalo así:

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

En general, prefiera hacer la asignación en la función de llamada , no dentro de la función en sí, de modo que pueda estar lo más abierto posible a las diferentes estrategias de asignación.

Jesse Beder
fuente
6

Dos enfoques diferentes:

  1. Pase sus valores de retorno por puntero y modifíquelos dentro de la función. Declara su función como nula, pero regresa a través de los valores pasados ​​como punteros.
  2. Defina una estructura que agregue sus valores de retorno.

Creo que el número 1 es un poco más obvio sobre lo que está sucediendo, aunque puede volverse tedioso si tiene demasiados valores de retorno. En ese caso, la opción # 2 funciona bastante bien, aunque hay una sobrecarga mental involucrada en la creación de estructuras especializadas para este propósito.

James Thompson
fuente
1
C no tiene referencias ;-), aunque dado que se usó el póster string, podría ser seguro asumir C ++ ...
Travis Gockel
¡Me olvidé por completo de eso! Modifiqué mi respuesta para usar punteros, pero claramente he estado en la tierra de C ++ durante demasiado tiempo. :)
James Thompson
6

Cree una estructura y establezca dos valores dentro y devuelva la variable de estructura.

struct result {
    int a;
    char *string;
}

Tienes que asignar espacio para el char *en tu programa.

zs2020
fuente
3

Utilice punteros como parámetros de su función. Luego utilícelos para devolver múltiples valores.

Bloodlee
fuente
2

Pasando parámetros por referencia a la función.

Ejemplos:

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

También mediante el uso de variables globales, pero no se recomienda.

Ejemplo:

int a=0;

void main(void)
{
    //Anything you want to code.
}
Badr
fuente
4
void main(void)¡Oh, cómo arde!
Chris Lutz
¿Qué quieres decir? @ Chris Lutz
Badr
6
mainse supone que devuelve un código de estado. En * nix, es más habitual declararlo como int main(int argc, char *argv[]), creo que Windows tiene convenciones similares.
Duncan
2

Un enfoque es utilizar macros. Coloque esto en un archivo de encabezadomultitype.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__)

Esto hace posible devolver hasta cuatro variables de una función y asignarlas a hasta cuatro variables. Como ejemplo, puede usarlos así:

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;
}

Esto es lo que imprime:

(55, 3.9, 24.15)

Es posible que la solución no sea tan portátil porque requiere C99 o posterior para macros variadic y declaraciones de variables for-statement. Pero creo que fue lo suficientemente interesante como para publicarlo aquí. Otro problema es que el compilador no te advertirá si les asignas valores incorrectos, por lo que debes tener cuidado.

En mi repositorio de github hay disponibles ejemplos adicionales y una versión del código basada en pilas que usa uniones .

Klas. S
fuente
1
Un problema de portabilidad mayor es la característica no estándar__typeof__
MM
En lugar de typeof, probablemente podría usar sizeof. Obtenga el tamaño de los valores devueltos y asigne una matriz lo suficientemente grande para almacenarlos todos. Entonces puedes devolverlo.
Klas. S