2008-10-15 20 views
290

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 una cantidad variable de argumentos desde dentro de ella, pasando todos los argumentos que entraron en la primera ¿función?Pasando el número variable de argumentos alrededor de

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

Tu ejemplo me parece un poco raro, ya que pasas fmt a format_string() y a fprintf(). ¿Debería format_string() devolver una nueva cadena de alguna manera? –

+2

El ejemplo no tiene sentido. Fue solo para mostrar el contorno del código. –

+139

"debe ser googleado": no estoy de acuerdo. Google tiene mucho ruido (información poco clara, a menudo confusa). ¡Tener un buen (votado, respuesta aceptada) en stackoverflow realmente ayuda! – Ansgar

Respuesta

179

Para pasar las elipses sobre, hay que convertirlos a un va_list y el uso que va_list en su 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); 
} 
+9

Escribo este comentario como una especie de anuncio de seguridad pública. Uno de mis alumnos se confundió con la llamada de fprintf al final, y pensó que no necesita enviar argptr como argumento. Para empeorar las cosas, en realidad funcionó, como lo hace a veces un comportamiento indefinido. Por lo tanto, tenga en cuenta que, como escribió Vicent Martí en un comentario a su pregunta, ¡la última llamada a fprintf no tiene sentido! –

+3

El código está tomado de la pregunta, y es realmente solo una ilustración de cómo convertir elipses en lugar de algo funcional. Si lo miras 'format_string' tampoco será útil, ya que tendría que hacer modificaciones in-situ a fmt, lo que ciertamente no debería hacerse tampoco. 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 si format_string devuelve una cadena diferente. Editaré la respuesta para mostrar lo último. –

+1

Si su cadena de formato usa los mismos comandos de cadena de formato que printf, también puede obtener algunos compiladores como gcc y clang para advertencias si su cadena de formato no es compatible con los argumentos reales que se pasaron. Consulte el atributo de función GCC 'formato' para más detalles: http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html. –

48

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

La solución utilizada generalmente es proporcionar siempre una forma alternativa de funciones vararg, por lo printf tiene vprintf que toma un va_list en lugar de la .... Las versiones ... son solo envoltorios de las versiones va_list.

+0

Tenga en cuenta que ['vsyslog'] (http://linux.die.net/man/3/vsyslog) es ** no ** [POSIX] (http://pubs.opengroup.org/onlinepubs/9699919799/) obediente. –

28

En magnífica C++ 0x se podía utilizar plantillas variadic:

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...); 
} 
+0

No olvide que las plantillas variadic aún no están disponibles en Visual Studio ... ¡por supuesto que esto no le concierne! –

+1

Si está usando Visual Studio, se pueden agregar plantillas variadic a Visual Studio 2012 usando el CTP de noviembre de 2012. Si está usando Visual Studio 2013, tendrá plantillas variadic. – user2023370

5

Puede usar ensamblador 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); 
    } 
+2

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

+4

Nuevo sin eliminar también. – user7116

+4

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

51

Variadic Functions puede haber dangerous. 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}); 
+8

Aún mejor es este truco: '#define callVardicMethodSafely (values ​​...) ({values ​​* v = {values}; _actualFunction (values, sizeof (v)/sizeof (* v));}) ' –

+3

@ RichardJ.RossIII Me gustaría que ampliaras tu comentario, es difícilmente legible así, No puedo entender la idea detrás del código y realmente se ve muy interesante y útil. – penelope

+0

@penelope, básicamente, crea una matriz a partir de los valores pasados ​​a la macro. Puede que solo funcione en GCC, pero no estoy seguro. –

1

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ío (tanto Visual Studio C++ como GCC).

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

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

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() lee de 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 cualquier var como punto de partida. Entonces, si lo desea, puede pasar punteros a otras funciones y usarlas también. Puede crear sus propias macros con la suficiente facilidad. Todas las macros son las direcciones de memoria de tipo de letra. Sin embargo, hacerlos funcionar para todos los compiladores y convenciones de llamadas es molesto. Por lo general, es más fácil usar los que vienen con el compilador.

1

También puedes probar macro.

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

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

¿O sí?

Si completa su función variadic en un macro, no necesita ningún arg anterior. Considere este ejemplo:

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

Esto obviamente es mucho más conveniente, ya que no necesita especificar el argumento inicial cada vez.

Cuestiones relacionadas