2008-09-03 12 views
43

Estoy buscando hacer esto en C/C++.Cómo envolver una función con argumentos de longitud variable?

Me encontré con Variable Length Arguments pero esto sugiere una solución con Python & C usando libffi.

Ahora, si quiero envolver printf función con myprintf

Lo que hago es, como a continuación:

void myprintf(char* fmt, ...) 
{ 
    va_list args; 
    va_start(args,fmt); 
    printf(fmt,args); 
    va_end(args); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int a = 9; 
    int b = 10; 
    char v = 'C'; 
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b); 
    return 0; 
} 

Pero los resultados no son los esperados!

This is a number: 1244780 and 
this is a character: h and 
another number: 29953463 

¿En qué punto perdí?

+2

La respuesta a esta pregunta es _very_ diferente ahora que C++ 11 está fuera. –

+0

@MooingDuck De hecho, agregué una respuesta de 'Plantillas variables', ¿cree que hay una manera más agradable en C++ 11? –

+0

@MooingDuck Una función vararg no es una función de plantilla variadic. Son diferentes en naturaleza y tipo. – rubenvb

Respuesta

63

el problema es que no se puede utilizar 'printf' con va_args. Debe usar vprintf si está utilizando listas de argumentos variables. VPrint, vsprintf, vfprintf, etc (hay también versiones 'seguros' en tiempo de ejecución C de Microsoft que evitarán desbordamientos de búfer, etc.)

Usted obras de la muestra de la siguiente manera:

void myprintf(char* fmt, ...) 
{ 
    va_list args; 
    va_start(args,fmt); 
    vprintf(fmt,args); 
    va_end(args); 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int a = 9; 
    int b = 10; 
    char v = 'C'; 
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n",a, v, b); 
    return 0; 
} 
+0

¿Puedes arrojar algo de luz sobre por qué "no puedes usar' printf' con 'va_args'"? ¿Por qué 'vprintf'? –

+1

@JohnStrood Para responder esto directamente: porque printf() no acepta una 'va_list' como argumento, espera una cantidad variable de argumentos (por ejemplo, "...") que es diferente. Consulte la página man para printf() y vprintf(). No, dónde dice que printf() acepta 'va_list' como argumento, solo una cantidad variable de argumentos del tipo que esperan los códigos de formato% (int, long, float, etc.). Solo la familia de funciones vprintf() acepta una va_list. – erco

1

¿Está utilizando C o C++? La próxima versión de C++, C++ 0x, admitirá variadic templates, que proporcionan una solución a ese problema.

Otra solución puede lograrse por el operador inteligente sobrecarga para lograr una sintaxis como esta:

void f(varargs va) { 
    BOOST_FOREACH(varargs::iterator i, va) 
     cout << *i << " "; 
} 

f(args = 1, 2, 3, "Hello"); 

Con el fin de conseguir que esto funcione, la clase varargs tiene que ser implementado para anular operator = que devuelve un objeto proxy que, a su vez, anula operator ,. Sin embargo, por lo que sé, hacer que este tipo de variante sea seguro en C++ actual no es posible, ya que tendría que funcionar por tipo de borrado.

+1

C++ 03 podría use 'boost :: tuple' que tiene formas de hacer lo anterior de manera segura. –

7

También estoy seguro de lo que entendemos por pura

En C++ utilizamos

#include <cstdarg> 
#include <cstdio> 

class Foo 
{ void Write(const char* pMsg, ...); 
}; 

void Foo::Write(const char* pMsg, ...) 
{ 
    char buffer[4096]; 
    std::va_list arg; 
    va_start(arg, pMsg); 
    std::vsnprintf(buffer, 4096, pMsg, arg); 
    va_end(arg); 
    ... 
} 
0
void myprintf(char* fmt, ...) 
{ 
    va_ list args; 
    va_ start(args,fmt); 
    printf(fmt,args); ----> This is the fault. vprintf(fmt, args); should have been used. 
    va_ end(args); 
} 
If you're just trying to call printf, 
there's a printf variant called vprintf that takes 
the va_list directly : vprintf(fmt, args); 
9

En C++ 11 esta es una solución posible utilizando Variadic templates:

template<typename... Args> 
void myprintf(const char* fmt, Args... args) 
{ 
    std::printf(fmt, args...) ; 
} 

EDITAR

Como @rubenvb señala que hay consideraciones que considerar, por ejemplo, generará código para cada instancia que dará lugar a la saturación del código.

2

En realidad, hay una forma de llamar a una función que no tiene una versión va_list desde un contenedor.La idea es usar ensamblador, no tocar argumentos en la pila, y reemplazar temporalmente la dirección de retorno de la función.

Ejemplo para Visual C x86. call addr_printf llamadas printf():

__declspec(thread) static void* _tls_ret; 

static void __stdcall saveret(void *retaddr) { 
    _tls_ret = retaddr; 
} 

static void* __stdcall _getret() { 
    return _tls_ret; 
} 

__declspec(naked) 
static void __stdcall restret_and_return_int(int retval) { 
    __asm { 
     call _getret 
     mov [esp], eax ; /* replace current retaddr with saved */ 
     mov eax, [esp+4] ; /* retval */ 
     ret 4 
    } 
} 

static void __stdcall _dbg_printf_beg(const char *fmt, va_list args) { 
    printf("calling printf(\"%s\")\n", fmt); 
} 

static void __stdcall _dbg_printf_end(int ret) { 
    printf("printf() returned %d\n", ret); 
} 

__declspec(naked) 
int dbg_printf(const char *fmt, ...) 
{ 
    static const void *addr_printf = printf; 
    /* prolog */ 
    __asm { 
     push ebp 
     mov ebp, esp 
     sub esp, __LOCAL_SIZE 
     nop 
    } 
    { 
     va_list args; 
     va_start(args, fmt); 
     _dbg_printf_beg(fmt, args); 
     va_end(args); 
    } 
    /* epilog */ 
    __asm { 
     mov esp, ebp 
     pop ebp 
    } 
    __asm { 
     call saveret 
     call addr_printf 
     push eax 
     push eax 
     call _dbg_printf_end 
     call restret_and_return_int 
    } 
} 
+1

Ni siquiera me atrevería a escribir esto, pero no puedo admirarlo. –

Cuestiones relacionadas