Wie kann ich eine Funktion mit Argumenten variabler Länge umschließen?

Lesezeit: 6 Minuten

Benutzeravatar von Prakash
Prakasch

Ich schaue, um dies in C/C++ zu tun. Ich bin gestoßen Argumente mit variabler Längeaber dies schlägt eine Lösung mit Python und C vor libffi.

Nun, wenn ich das wickeln will printf Funktion mit myprintf.

Ich mache es wie folgt:

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

Aber die Ergebnisse sind nicht wie erwartet!

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

Was habe ich verpasst?

  • Die Antwort auf diese Frage lautet sehr anders, jetzt wo C++11 draußen ist.

    – Muhende Ente

    4. März 2013 um 22:01 Uhr


  • @MooingDuck Tatsächlich habe ich a hinzugefügt Variadic templates Antwort, glauben Sie, dass es in C++11 einen schöneren Weg gibt?

    – Shafik Yaghmour

    24. Juli 2013 um 14:47 Uhr

  • @MooingDuck Eine vararg-Funktion ist keine variadische Vorlagenfunktion. Sie unterscheiden sich in Art und Art.

    – rubenvb

    24. Juli 2013 um 15:05 Uhr

  • @rubenvb In diesem Fall denke ich nicht, dass die Unterscheidung wichtig ist, fast alle Referenzen, die ich gesehen habe, preisen sie als direkten Ersatz für die variadische Funktion an: en.cppreference.com/w/cpp/utility/variadic Ich wäre also neugierig, die Unterscheidung zu verstehen, die Sie in diesem Fall sehen.

    – Shafik Yaghmour

    24. Juli 2013 um 15:17 Uhr

  • @shafik: Wie wäre es mit dem offensichtlichen Code-Bloat für jede Instanziierung? Wie wäre es mit der Übergabe von Funktionszeigern? Es gibt einige Unterschiede, die Sie beachten müssen. Ich sage nicht, dass Sie es nicht tun sollten, ich sage nur, dass niemand variadische Funktionen abgelehnt hat.

    – rubenvb

    24. Juli 2013 um 18:14 Uhr

Marks Benutzeravatar
Markieren

Das Problem ist, dass Sie ‘printf’ nicht mit verwenden können va_args. Sie müssen verwenden vprintf wenn Sie variable Argumentlisten verwenden. vprint, vsprintf, vfprintfusw. (es gibt auch “sichere” Versionen in Microsofts C-Laufzeitumgebung, die Pufferüberläufe usw. verhindern.)

Sie probieren Werke wie folgt aus:

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

  • Können Sie erklären, warum „Sie nicht verwenden können printf mit va_args” ? Warum vprintf?

    – John Strood

    29. Oktober 2016 um 17:10 Uhr

  • @JohnStrood Um dies direkt zu beantworten: Da printf() keine ‘va_list’ als Argument akzeptiert, erwartet es eine variable Anzahl von Argumenten (z. B. “…”), die unterschiedlich ist. Siehe die Manpage für printf() und vprintf(). Nein, wo steht, dass printf() ‘va_list’ als Argument akzeptiert, nur eine variable Anzahl von Argumenten des Typs, den die %-Formatcodes erwarten (int, long, float usw.). Nur die Funktionsfamilie vprintf() akzeptiert eine va_list.

    – erko

    2. Oktober 2017 um 23:17 Uhr

  • 1 Sie müssen const char verwenden 2 Sie können fmt definitiv nicht als Zeichenfolgenpuffer für vprintf verwenden … haben Sie diesen Code getestet ???

    – Benutzer7082181

    28. Mai 2021 um 18:02 Uhr


Benutzeravatar von Shafik Yaghmour
Shafik Yaghmur

Im C++11dies ist eine mögliche Lösung mit abwechslungsreiche Vorlagen:

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

Wie rubenvb betont, sind Kompromisse zu berücksichtigen. Beispielsweise generieren Sie Code für jede Instanz, was zu Code-Bloat führt.

  • Seien Sie auch gewarnt, dass die Überprüfung des Argumentformats der Funktionsfamilien printf und scanf nicht mit Vorlagen funktioniert. Der Formatstring wird nicht überprüft. Wenn Sie den Format-String falsch erhalten, wird er zur Kompilierzeit nicht abgefangen, kann aber einen Segfault (Absturz) haben oder zur Laufzeit ein unbekanntes Verhalten zeigen.

    – Chris Reid

    19. Juli 2018 um 23:42 Uhr


  • Vielen Dank! Ich habe dies mit dem nios2-elf-gcc-Compiler getestet und es funktioniert wie ein Zauber!

    – Andak

    14. Januar um 14:35 Uhr

Benutzeravatar von David Sykes
David Syke

Ich bin mir auch nicht sicher, was du mit rein meinst.

In C++ verwenden wir:

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

  • Sie können der Funktionsdefinition ein Compilerattribut hinzufügen und den Compiler Ihre Formatargumente überprüfen lassen. Klasse Foo { Attribut ((format (printf, 2, 3))) void Write(const char* pMsg, …); }; Foo f; f.Write( “%s %s %d %s” , “Hund” , “Katze”, “Pferd”, “Schwein”); “Warnung: Format gibt den Typ ‘int’ an, aber das Argument hat den Typ ‘const char *’ [-Wformat]”

    – Chris Reid

    19. Juli 2018 um 23:57 Uhr


Benutzeravatar des Beckens
Becken

Tatsächlich gibt es eine Möglichkeit, eine Funktion aufzurufen, die kein hat va_list Version aus einem Wrapper. Die Idee ist, Assembler zu verwenden, Argumente auf dem Stapel nicht zu berühren und die Rückgabeadresse der Funktion vorübergehend zu ersetzen.

Ein Beispiel für Visual C x86. call addr_printf Anrufe 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
    }
}

Benutzt du C oder C++? Die nächste C++-Version, C++0x, wird unterstützt abwechslungsreiche Vorlagen die eine Lösung für dieses Problem bieten.

Eine weitere Problemumgehung kann durch geschicktes Überladen von Operatoren erreicht werden, um eine Syntax wie die folgende zu erreichen:

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

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

Damit das funktioniert, muss die Klasse varargs muss zum Überschreiben implementiert werden operator = das ein Proxy-Objekt zurückgibt, das wiederum überschreibt operator ,. Allerdings ist es meines Wissens nicht möglich, diesen Variantentyp in aktuellem C++ sicher zu machen, da es durch Typlöschung funktionieren müsste.

  • C++03 verwenden könnte boost::tuple die Möglichkeiten hat, die oben genannten Dinge sicher zu tun.

    – Muhende Ente

    4. März 2013 um 22:03 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

Wie meinen Sie eine reine C/C++-Lösung?

Der rest-Parameter (…) wird in der C-Laufzeit plattformübergreifend unterstützt.

va_arg, va_copy, va_end, va_start

  • C++03 verwenden könnte boost::tuple die Möglichkeiten hat, die oben genannten Dinge sicher zu tun.

    – Muhende Ente

    4. März 2013 um 22:03 Uhr

Benutzeravatar von Peter Mortensen
Peter Mortensen

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

Wenn Sie nur versuchen anzurufen DruckfDa ist ein Druckf Variante genannt vprintf das nimmt die va_liste direkt: vprintf(fmt, args);

1409610cookie-checkWie kann ich eine Funktion mit Argumenten variabler Länge umschließen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy