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

83

Ich habe eine Klasse, die eine "Fehler" -Funktion enthält, die Text formatiert. Ich möchte eine variable Anzahl von Argumenten akzeptieren und sie dann mit printf formatieren.

Beispiel:

class MyClass
{
public:
    void Error(const char* format, ...);
};

Die Error-Methode sollte die Parameter übernehmen, printf / sprintf aufrufen, um sie zu formatieren, und dann etwas damit tun. Ich möchte nicht die gesamte Formatierung selbst schreiben, daher ist es sinnvoll, herauszufinden, wie die vorhandene Formatierung verwendet wird.

user5722
quelle

Antworten:

150
void Error(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(stderr, format, argptr);
    va_end(argptr);
}

Wenn Sie die Zeichenfolge bearbeiten möchten, bevor Sie sie anzeigen, und sie tatsächlich zuerst in einem Puffer gespeichert werden müssen, verwenden Sie vsnprintfstatt vsprintf. vsnprintfverhindert einen versehentlichen Pufferüberlauffehler.

John Kugelman
quelle
35

Schauen Sie sich vsnprintf an, da dies das tut, was Sie wollen. http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

Sie müssen zuerst das Array va_list arg initialisieren und dann aufrufen.

Beispiel von diesem Link: / * vsprintf Beispiel * /

#include <stdio.h>
#include <stdarg.h>

void Error (char * format, ...)
{
  char buffer[256];
  va_list args;
  va_start (args, format);
  vsnprintf (buffer, 255, format, args);


  //do something with the error

  va_end (args);
}
Lodle
quelle
6
Das zweite Argument von vsnprintf sollte die Pufferlänge sein, einschließlich des abschließenden Nullbytes ('\ 0'). Sie können also 256 im Funktionsaufruf anstelle von 255 verwenden.
Aviggiano
und magische Zahlen zu übergeben ist SCHLECHT ... sizeof(buffer)anstelle von 256 verwenden.
Anonymouse
4

Ich hätte mehr über bestehende Fragen im Stapelüberlauf lesen sollen.

C ++ Übergeben der Variablen Anzahl der Argumente ist eine ähnliche Frage. Mike F hat die folgende Erklärung:

Es gibt keine Möglichkeit, (z. B.) printf aufzurufen, ohne zu wissen, wie viele Argumente Sie an es weitergeben, es sei denn, Sie möchten sich auf freche und nicht tragbare Tricks einlassen.

Die allgemein verwendete Lösung besteht darin, immer eine alternative Form von Vararg-Funktionen bereitzustellen. Printf hat also vprintf, das eine va_list anstelle der .... verwendet. Die ... -Versionen sind nur Wrapper um die va_list-Versionen.

Genau das habe ich gesucht. Ich habe eine Testimplementierung wie folgt durchgeführt:

void Error(const char* format, ...)
{
    char dest[1024 * 16];
    va_list argptr;
    va_start(argptr, format);
    vsprintf(dest, format, argptr);
    va_end(argptr);
    printf(dest);
}
user5722
quelle
Das letzte 'printf (dest);' ist falsch geformt - es benötigt mindestens eine Formatzeichenfolge.
Jonathan Leffler
Dies ist nicht der Fall, da die Zeichenfolge die Formatzeichenfolge ist, dh printf ("eine Zeichenfolge"). ist in Ordnung
Lodle
4
Sie können mit printf (dest) davonkommen, bis dest zufällig "% s" oder "% d" enthält, dann BOOM . Bitte verwenden Sie printf ("% s", dest).
John Kugelman
Ich möchte nur vorbeikommen, um darauf hinzuweisen, dass ein Core-Dump das beste Szenario ist. Tun Sie dies im Server-Code, und Hacker haben Ihre CPU zum Frühstück.
MickLH
Versuchen Sie es mit "% .16383s", um das Array-Ziel vor Überlauf zu schützen. (
Erlaube den
3

Sie suchen nach variadischen Funktionen . printf () und sprintf () sind verschiedene Funktionen - sie können eine variable Anzahl von Argumenten akzeptieren.

Dies beinhaltet im Wesentlichen die folgenden Schritte:

  1. Der erste Parameter muss einen Hinweis auf die Anzahl der folgenden Parameter geben. In printf () gibt der Parameter "format" diesen Hinweis an. Wenn Sie 5 Formatspezifizierer haben, sucht er nach 5 weiteren Argumenten (für insgesamt 6 Argumente). Das erste Argument kann eine Ganzzahl sein (z. B. "myfunction" (3, a, b, c) "wobei" 3 "" 3 Argumente "bedeutet)

  2. Durchlaufen Sie dann jedes aufeinanderfolgende Argument und rufen Sie es mit den Funktionen va_start () usw. ab.

Es gibt viele Tutorials dazu - viel Glück!

Poundifdef
quelle
3

Die Verwendung von Funktionen mit den Ellipsen ist nicht sehr sicher. Wenn die Leistung für die Protokollfunktion nicht kritisch ist, sollten Sie die Operatorüberladung wie im boost :: -Format verwenden. Sie könnten so etwas schreiben:

#include <sstream>
#include <boost/format.hpp>
#include <iostream>
using namespace std;

class formatted_log_t {
public:
    formatted_log_t(const char* msg ) : fmt(msg) {}
    ~formatted_log_t() { cout << fmt << endl; }

    template <typename T>
    formatted_log_t& operator %(T value) {
        fmt % value;
        return *this;
    }

protected:
    boost::format                fmt;
};

formatted_log_t log(const char* msg) { return formatted_log_t( msg ); }

// use
int main ()
{
    log("hello %s in %d-th time") % "world" % 10000000;
    return 0;
}

Das folgende Beispiel zeigt mögliche Fehler mit Ellipsen:

int x = SOME_VALUE;
double y = SOME_MORE_VALUE;
printf( "some var = %f, other one %f", y, x ); // no errors at compile time, but error at runtime. compiler do not know types you wanted
log( "some var = %f, other one %f" ) % y % x; // no errors. %f only for compatibility. you could write %1% instead.
Kirill V. Lyadvinsky
quelle
5
So macht man es sich leicht.
Eddyq
2
"Die Verwendung von Funktionen mit Ellipsen ist nicht sehr sicher." Wenn Ihre einzige sichere Alternative C ++ und Boost umfasst, sollten Sie erklären, was Sie unter "nicht sehr sicher" verstehen, und erwähnen, dass die printf-Funktionen absolut sicher sind, wenn Sie die richtigen Formatspezifizierer verwenden.
Osvein
2

Einfaches Beispiel unten. Beachten Sie, dass Sie einen größeren Puffer übergeben und testen sollten, ob der Puffer groß genug war oder nicht

void Log(LPCWSTR pFormat, ...) 
{
    va_list pArg;
    va_start(pArg, pFormat);
    char buf[1000];
    int len = _vsntprintf(buf, 1000, pFormat, pArg);
    va_end(pArg);
    //do something with buf
}
DougN
quelle