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);
 }
Vicent Marti
fuente
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);
}
SmacL
fuente
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.


fuente
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});
Rose Perrone
fuente
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...);
}
user2023370
fuente
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);
    }
Yoda
fuente
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;
}
GeekyJ
fuente
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.

VarunG
fuente
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);}
BSalita
fuente
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.

Ingeniero
fuente
-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.

Jim
fuente