Pasar un número variable de argumentos alrededor


333

Digamos que tengo una función C que toma un número variable de argumentos: ¿Cómo puedo llamar a otra función que espera un número variable de argumentos dentro de ella, pasando todos los argumentos que entraron en la primera función?

Ejemplo:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }

44
Su ejemplo me parece un poco extraño, ya que pasa fmt tanto a format_string () como a fprintf (). ¿Debería format_string () devolver una nueva cadena de alguna manera?
— Kristopher Johnson

2
El ejemplo no tiene sentido. Era solo para mostrar el esquema del código.
— Vicent Marti

162
"debería estar en google": no estoy de acuerdo. Google tiene mucho ruido (información poco clara, a menudo confusa). ¡Tener una buena (respuesta votada y aceptada) en stackoverflow realmente ayuda!
— Ansgar

71
Solo para tener en cuenta: llegué a esta pregunta de google, y debido a que era un desbordamiento de la pila, confiaba mucho en que la respuesta sería útil. ¡Así que pregunta!
— tenpn

32
@Ilya: si nadie anotara cosas fuera de Google, no habría información para buscar en Google.
— Erik Kaplun

Respuestas:


211

Para pasar los puntos suspensivos, debes convertirlos en va_list y usar esa va_list en tu segunda función. Específicamente;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}

3
El código está tomado de la pregunta, y en realidad es solo una ilustración de cómo convertir elipses en lugar de algo funcional. Si lo mira, tampoco format_stringserá útil, ya que tendría que hacer modificaciones in situ a fmt, lo que ciertamente tampoco debería hacerse. Las opciones incluirían deshacerse de format_string por completo y usar vfprintf, pero eso hace suposiciones sobre lo que format_string realmente hace, o que format_string devuelva una cadena diferente. Editaré la respuesta para mostrar la última.
— SmacL

1
Si su cadena de formato utiliza los mismos comandos de cadena de formato que printf, también puede obtener algunos compiladores como gcc y clang para que le den advertencias si su cadena de formato no es compatible con los argumentos reales pasados. Vea el atributo de función GCC ' formato 'para más detalles: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
— Doug Richardson el

1
Esto no parece funcionar si está pasando los argumentos dos veces seguidas.
— fotanus

2
@fotanus: si llama a una función con argptry la función llamada usa la función argptr, lo único seguro es llamar va_end()y luego reiniciar va_start(argptr, fmt);para reiniciar. O puede usarlo va_copy()si su sistema lo admite (C99 y C11 lo requieren; C89 / 90 no).
— Jonathan Leffler

1
Tenga en cuenta que el comentario de @ ThomasPadron-McCarthy ahora está desactualizado y el fprintf final está bien.
— Frederick

59

No hay forma de llamar a (por ejemplo) printf sin saber cuántos argumentos le está pasando, a menos que quiera meterse en trucos traviesos y no portátiles.

La solución utilizada generalmente es proporcionar siempre una forma alternativa de funciones vararg, por lo que printftiene vprintfque tiene una va_listen lugar de la .... Las ...versiones son solo envoltorios alrededor de las va_listversiones.


Tenga en cuenta que novsyslog es compatible con POSIX .
— patryk.beza

53

Las funciones variables pueden ser peligrosas . Aquí hay un truco más seguro:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});

11
Aún mejor es este truco: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
— Richard J. Ross III

55
@ RichardJ.RossIII Desearía que ampliaras tu comentario, es difícil de leer así, no puedo entender la idea detrás del código y en realidad se ve muy interesante y útil.
— Penélope

55
@ArtOfWarfare No estoy seguro de estar de acuerdo en que es un mal truco, Rose tiene una gran solución pero implica escribir func ((tipo []) {val1, val2, 0}); que se siente torpe, cuando tenías #define func_short_cut (...) func ((type []) { VA_ARGS }); entonces simplemente puede llamar a func_short_cut (1, 2, 3, 4, 0); lo que le da la misma sintaxis que una función variable normal con el beneficio adicional del truco de Rose ... ¿cuál es el problema aquí?
— chrispepper1989

99
¿Qué pasa si quieres pasar 0 como argumento?
— Julian Gold

1
Esto requiere que sus usuarios recuerden llamar con una terminación 0. ¿Cómo es más seguro?
— cp.engr

29

En el magnífico C ++ 0x podría usar plantillas variadas:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}

No olvide que las plantillas variadas aún no están disponibles en Visual Studio ... ¡esto puede no ser una preocupación para usted, por supuesto!
— Tom Swirly

1
Si está utilizando Visual Studio, se pueden agregar plantillas variadas a Visual Studio 2012 utilizando el CTP de noviembre de 2012. Si está utilizando Visual Studio 2013, tendrá plantillas variadas.
— user2023370

7

Puede usar el ensamblaje en línea para la llamada a la función. (en este código supongo que los argumentos son caracteres).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }

44
No es portátil, depende del compilador y evita la optimización del compilador. Muy mala solución.
— Geoffroy

44
Nuevo sin eliminar también.
— user7116

8
Al menos esto realmente responde la pregunta, sin redefinir la pregunta.
— lama12345

6

Puedes probar macro también.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}

6

Aunque puede resolver pasar el formateador almacenándolo primero en el búfer local, pero eso necesita apilarse y en algún momento puede ser un problema para tratar. Intenté seguir y parece funcionar bien.

#include <stdarg.h>
#include <stdio.h>

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Espero que esto ayude.


2

La solución de Ross se limpió un poco. Solo funciona si todos los argumentos son punteros. Además, la implementación del lenguaje debe admitir la eliminación de la coma anterior si __VA_ARGS__está vacía (tanto Visual Studio C ++ como GCC lo hacen).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

0

Digamos que tiene una función variadic típica que ha escrito. Porque se requiere al menos un argumento antes del variable... , siempre debe escribir un argumento adicional en uso.

O tu

Si ajusta su función variadic en una macro, no necesita argumentos anteriores. Considere este ejemplo:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Obviamente, esto es mucho más conveniente, ya que no es necesario especificar el argumento inicial cada vez.


-5

No estoy seguro de si esto funciona para todos los compiladores, pero ha funcionado hasta ahora para mí.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Puede agregar el ... a inner_func () si lo desea, pero no lo necesita. Funciona porque va_start usa la dirección de la variable dada como punto de inicio. En este caso, le estamos dando una referencia a una variable en func (). Entonces usa esa dirección y lee las variables después de eso en la pila. La función inner_func () está leyendo desde la dirección de pila de func (). Por lo tanto, solo funciona si ambas funciones usan el mismo segmento de pila.

Las macros va_start y va_arg generalmente funcionarán si les das alguna var como punto de partida. Entonces, si lo desea, puede pasar punteros a otras funciones y usarlas también. Puede hacer sus propias macros fácilmente. Todo lo que hacen las macros es escribir direcciones de memoria. Sin embargo, hacer que funcionen para todos los compiladores y llamar a convenciones es molesto. Por lo tanto, generalmente es más fácil usar los que vienen con el compilador.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.