Pasar argumentos de variable a otra función que acepta una lista de argumentos de variable

114

Entonces tengo 2 funciones que ambas tienen argumentos similares

void example(int a, int b, ...);
void exampleB(int b, ...);

Ahora examplellama exampleB, pero ¿cómo puedo pasar las variables en la lista de argumentos de variable sin modificar exampleB(ya que esto ya se usa en otros lugares también)?

No disponible
fuente
6
posible duplicado de Reenviar una invocación de una función variada en C
Greg Hewgill
2
Bueno, la solución en eso fue usar vprintf, y ese no es el caso aquí.
No disponible el
Esto está relacionado con el duplicado propuesto, pero definitivamente no es lo mismo: ¿ Reenviar una invocación de una función variada en C?
Jonathan Leffler

Respuestas:

127

No puede hacerlo directamente; tienes que crear una función que tome un va_list:

#include <stdarg.h>

static void exampleV(int b, va_list args);

void exampleA(int a, int b, ...)    // Renamed for consistency
{
    va_list args;
    do_something(a);                // Use argument a somehow
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

void exampleB(int b, ...)
{
    va_list args;
    va_start(args, b);
    exampleV(b, args);
    va_end(args);
}

static void exampleV(int b, va_list args)
{
    ...whatever you planned to have exampleB do...
    ...except it calls neither va_start nor va_end...
}
Jonathan Leffler
fuente
2
Se sospecha que tenía que hacer algo como esto, el problema es que la función de ejemplo es básicamente un contenedor para vsprintf y no mucho más: /
No disponible el
@Xeross: Tenga en cuenta que esto no cambia la especificación externa de lo que hace exampleB, solo cambia la implementación interna. No estoy seguro de cuál es el problema.
Jonathan Leffler
¿Se requiere el primer parámetro o todos los parámetros pueden ser variables?
Qwerty
1
@Qwerty: La sintaxis requiere un primer argumento con nombre para una función que toma una lista de argumentos variables con , ...la firma. Si ha convertido los argumentos de la variable en a va_list, puede pasar el va_lista otra función que solo tome a va_list, pero esa función (o una a la que llama) debe tener alguna forma de saber qué hay en va_list.
Jonathan Leffler
46

Tal vez arrojar una piedra en un estanque aquí, pero parece funcionar bastante bien con las plantillas variadas de C ++ 11:

#include <stdio.h>

template<typename... Args> void test(const char * f, Args... args) {
  printf(f, args...);
}

int main()
{
  int a = 2;
  test("%s\n", "test");
  test("%s %d %d %p\n", "second test", 2, a, &a);
}

Al menos, funciona con g++.

Vincent Fourmond
fuente
Estoy confundido, ¿es este un enfoque legítimo usando C ++> = 11?
Duncan Jones
@DuncanJones Sí, el paquete se expande porargs...
Swordfish
15

debe crear versiones de estas funciones que tomen un va_list y las pasen. Mire vprintfcomo ejemplo:

int vprintf ( const char * format, va_list arg );
diez cuatro
fuente
5

También quería ajustar printf y encontré una respuesta útil aquí:

Cómo pasar un número variable de argumentos a printf / sprintf

No estaba interesado en absoluto en el rendimiento (estoy seguro de que este fragmento de código se puede mejorar de varias maneras, no dude en hacerlo :)), esto es solo para depuración general, así que hice esto:

//Helper function
std::string osprintf(const char *fmt, ...)
{
    va_list args;
    char buf[1000];
    va_start(args, fmt);
    vsnprintf(buf, sizeof(buf), fmt, args );
    va_end(args);
    return buf;
}

que luego puedo usar así

Point2d p;

cout << osprintf("Point2d: (%3i, %3i)", p.x, p.y);
instead of for example:
cout << "Point2d: ( " << setw(3) << p.x << ", " << p.y << " )";

Los ostreams de c ++ son hermosos en algunos aspectos, pero prácticamente se vuelven horribles si desea imprimir algo como esto con algunas cadenas pequeñas como paréntesis, dos puntos y comas insertadas entre los números.

Axel
fuente
2

Por cierto, muchas implementaciones de C tienen una variación interna de v? Printf que, en mi humilde opinión, debería haber sido parte del estándar C. Los detalles exactos varían, pero una implementación típica aceptará una estructura que contenga un puntero de función de salida de caracteres e información que indique lo que se supone que debe suceder. Esto permite que printf, sprintf y fprintf utilicen el mismo mecanismo 'central'. Por ejemplo, vsprintf podría ser algo como:

void s_out (PRINTF_INFO * p_inf, char ch)
{
  (* (p_inf-> destptr) ++) = ch;
  p_inf-> resultado ++;
}

int vsprintf (char * dest, const char * fmt, argumentos de lista_va)
{
  PRINTF_INFO p_inf;
  p_inf.destptr = dest;
  p_inf.result = 0;
  p_inf.func = s_out;
  core_printf (& p_inf, fmt, args);
}

La función core_printf entonces llama a p_inf-> func para cada carácter que se va a generar; la función de salida puede enviar los caracteres a la consola, un archivo, una cadena o algo más. Si la implementación de uno expone la función core_printf (y cualquier mecanismo de configuración que utilice), se puede extender con todo tipo de variaciones.

Super gato
fuente
1

Una forma posible es usar #define:

#define exampleB(int b, ...)  example(0, b, __VA_ARGS__)
Shai
fuente
0

Basado en el comentario que está terminando vsprintf, y que esto está etiquetado como C ++, sugiero que no intente hacer esto, sino que cambie su interfaz para usar iostreams C ++ en su lugar. Tienen ventajas sobre la printlínea de funciones, como la seguridad de tipos y la posibilidad de imprimir elementos que printfno podrían manipularse. Un poco de retrabajo ahora podría ahorrar una cantidad significativa de dolor en el futuro.

Marca B
fuente
¿A qué ventajas te refieres?
cjcurrie
@cjcurrie: la ventaja es la seguridad de tipos, incluso con tipos definidos por el usuario. Las funciones C no pueden manejar tipos definidos por el usuario en absoluto, por supuesto.
Jonathan Leffler
-1

Al usar el nuevo estándar C ++ 0x, es posible que pueda hacer esto usando plantillas variadas o incluso convertir ese código antiguo a la sintaxis de la nueva plantilla sin romper nada.

David
fuente
desafortunadamente, esto no es posible en todos los casos, solo intente usar lambdas
serup
-1

Esta es la única forma de hacerlo ... y la mejor forma de hacerlo también ...

static BOOL(__cdecl *OriginalVarArgsFunction)(BYTE variable1, char* format, ...)(0x12345678); //TODO: change address lolz

BOOL __cdecl HookedVarArgsFunction(BYTE variable1, char* format, ...)
{
    BOOL res;

    va_list vl;
    va_start(vl, format);

    // Get variable arguments count from disasm. -2 because of existing 'format', 'variable1'
    uint32_t argCount = *((uint8_t*)_ReturnAddress() + 2) / sizeof(void*) - 2;
    printf("arg count = %d\n", argCount);

    // ((int( __cdecl* )(const char*, ...))&oldCode)(fmt, ...);
    __asm
    {
        mov eax, argCount
        test eax, eax
        je noLoop
        mov edx, vl
        loop1 :
        push dword ptr[edx + eax * 4 - 4]
        sub eax, 1
        jnz loop1
        noLoop :
        push format
        push variable1
        //lea eax, [oldCode] // oldCode - original function pointer
        mov eax, OriginalVarArgsFunction
        call eax
        mov res, eax
        mov eax, argCount
        lea eax, [eax * 4 + 8] //+8 because 2 parameters (format and variable1)
        add esp, eax
    }
    return res;
}
SSpoke
fuente