Übergeben von Variablenargumenten an eine andere Funktion, die eine Liste mit Variablenargumenten akzeptiert

114

Ich habe also zwei Funktionen, die beide ähnliche Argumente haben

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

Nun exampleruft exampleB, aber wie kann ich entlang der Variablen in der Variablenargumentliste übergeben , ohne Modifizierung exampleB(wie dies bereits an anderer Stelle auch verwendet wird ).

Nicht verfügbar
quelle
6
mögliches Duplikat von Forward einen Aufruf einer variadischen Funktion in C
Greg Hewgill
2
Nun, die Lösung für dieses Problem war die Verwendung von vprintf, und das ist hier nicht der Fall.
Nicht verfügbar
Dies hängt mit dem vorgeschlagenen Duplikat zusammen, ist aber definitiv nicht dasselbe: Weiterleiten eines Aufrufs einer variadischen Funktion in C?
Jonathan Leffler

Antworten:

127

Sie können es nicht direkt tun; Sie müssen eine Funktion erstellen, die Folgendes übernimmt 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
quelle
2
Vermutlich musste ich so etwas tun, das Problem ist, dass die Beispielfunktion im Grunde ein Wrapper für vsprintf ist und nicht viel anderes: /
Nicht verfügbar
@Xeross: Beachten Sie, dass dies nicht die externe Spezifikation von Beispiel B ändert, sondern nur die interne Implementierung. Ich bin mir nicht sicher, wo das Problem liegt.
Jonathan Leffler
Ist der erste Parameter erforderlich oder können alle Parameter variabel sein?
Qwerty
1
@Qwerty: Die Syntax erfordert ein benanntes erstes Argument für eine Funktion, die eine variable Argumentliste , ...in der Signatur enthält. Wenn Sie die Variablenargumente in a konvertiert haben va_list, können Sie die va_listan eine andere Funktion übergeben, die nur a akzeptiert va_list, aber diese Funktion (oder eine, die sie aufruft) muss eine Möglichkeit haben, zu wissen, was in der enthalten ist va_list.
Jonathan Leffler
46

Vielleicht werfen Sie hier einen Stein in einen Teich, aber mit variablen C ++ 11-Vorlagen scheint es ziemlich in Ordnung zu sein:

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

Zumindest funktioniert es mit g++.

Vincent Fourmond
quelle
Ich bin verwirrt - ist dies ein legitimer Ansatz mit C ++> = 11?
Duncan Jones
@ DuncanJones Ja, das Paket wird erweitert durchargs...
Schwertfisch
15

Sie sollten Versionen dieser Funktionen erstellen, die eine va_list enthalten, und diese übergeben. Schauen Sie sich vprintfals Beispiel:

int vprintf ( const char * format, va_list arg );
zehn vier
quelle
5

Ich wollte auch printf einpacken und fand hier eine hilfreiche Antwort:

So übergeben Sie eine variable Anzahl von Argumenten an printf / sprintf

Ich war überhaupt nicht an Leistung interessiert (ich bin sicher, dass dieser Code auf verschiedene Weise verbessert werden kann, zögern Sie nicht :)), dies ist nur für das allgemeine Debugprinting gedacht, also habe ich Folgendes getan:

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

was ich dann so nutzen kann

Point2d p;

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

Die c ++ - Ostreams sind in einigen Aspekten wunderschön, aber praktisch werden sie schrecklich, wenn Sie so etwas mit kleinen Zeichenfolgen wie Klammern, Doppelpunkten und Kommas zwischen den Zahlen drucken möchten.

Axel
quelle
2

Übrigens haben viele C-Implementierungen eine interne v? Printf-Variante, die meiner Meinung nach Teil des C-Standards sein sollte. Die genauen Details variieren, aber eine typische Implementierung akzeptiert eine Struktur, die einen Zeichenausgabefunktionszeiger und Informationen enthält, die angeben, was passieren soll. Auf diese Weise können printf, sprintf und fprintf denselben 'Kern'-Mechanismus verwenden. Zum Beispiel könnte vsprintf so etwas sein wie:

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

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

Die Funktion core_printf ruft dann p_inf-> func für jedes auszugebende Zeichen auf. Die Ausgabefunktion kann dann die Zeichen an die Konsole, eine Datei, eine Zeichenfolge oder etwas anderes senden. Wenn die Implementierung die Funktion core_printf (und den verwendeten Setup-Mechanismus) verfügbar macht, kann sie mit allen möglichen Variationen erweitert werden.

Superkatze
quelle
1

Eine mögliche Möglichkeit ist die Verwendung von #define:

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

Basierend auf dem Kommentar, den Sie einschließen vsprintfund der als C ++ gekennzeichnet ist, würde ich vorschlagen, dies nicht zu versuchen, sondern Ihre Benutzeroberfläche so zu ändern, dass stattdessen C ++ - iostreams verwendet werden. Sie haben Vorteile gegenüber den printFunktionen, wie z. B. die Typensicherheit und die Möglichkeit, Elemente zu drucken, printfdie nicht verarbeitet werden können. Einige Überarbeitungen könnten in Zukunft erhebliche Schmerzen ersparen.

Mark B.
quelle
Auf welche Vorteile beziehen Sie sich?
cjcurrie
@cjcurrie: Der Vorteil ist die Typensicherheit, auch bei benutzerdefinierten Typen. Die C-Funktionen können natürlich überhaupt keine benutzerdefinierten Typen verarbeiten.
Jonathan Leffler
-1

Mit dem neuen C ++ 0x-Standard können Sie dies möglicherweise mithilfe verschiedener Vorlagen erledigen oder sogar den alten Code in die neue Vorlagensyntax konvertieren, ohne etwas zu beschädigen.

David
quelle
Leider ist dies nicht in allen Fällen möglich - versuchen Sie es einfach mit Lambdas
Serup
-1

Dies ist der einzige Weg, es zu tun .. und der beste Weg, es auch zu tun ..

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
quelle