2010-04-08 12 views
27

Gibt es eine Möglichkeit, die Länge von va_list zu berechnen? Alle Beispiele, in denen ich die Anzahl der Variablenparameter gesehen habe, sind explizit angegeben.Länge von va_list bei Verwendung von Variablenlistenargumenten?

+0

Jede Implementierung von 'va_arg', die ich gesehen habe, bewegt einfach den Zeiger' sizeof (arg_type) 'bytes, so dass es keine Möglichkeit gibt, die Länge zu kennen; das ist dein Job. –

Antwort

24

Es gibt keine Möglichkeit, die Länge eines va_list zu berechnen, deshalb benötigen Sie die Formatzeichenfolge in printf ähnliche Funktionen.

Die einzige Funktionen verfügbaren Makros für mit einem Arbeits va_list are:

  • va_start - beginnen die Verwendung va_list
  • va_arg-
  • nächste Argument bekommen
  • va_end - stoppen Sie die va_list
  • va_copy mit (seit C++ 11 und C99) - kopieren Sie die va_list

Bitte beachten Sie, dass Sie va_start und va_end im gleichen Umfang nennen müssen, was bedeutet, Sie nicht, dass es in einer Utility-Klasse wickeln können, die va_start in seinem Konstruktor und va_end in seinem destructor nennt (wurde ich damit einmal gebissen).

Zum Beispiel dieser Klasse ist wertlos:

class arg_list { 
    va_list vl; 
public: 
    arg_list(const int& n) { va_start(vl, n); } 
    ~arg_list() { va_end(vl); } 
    int arg() { 
     return static_cast<int>(va_arg(vl, int); 
    } 
}; 

GCC gibt die following error

t.cpp: In constructor arg_list::arg_list(const int&) :
Line 7: error: va_start used in function with fixed args
compilation terminated due to -Wfatal-errors.

+4

Hmm? Nein, es ist absolut erlaubt, 'va_start()' zu nennen, dann übergebe 'va_arg' an eine andere Funktion und rufe 'va_end()' auf. Genau so benutzen Sie die 'vsprintf()' und ähnliche Funktionen. – caf

+1

@caf: In welchem ​​Universum entkommt die va_list-Instanz im Fall der Verwendung von vsprintf() dem Geltungsbereich? –

+0

@caf du hast Recht, ich habe das nicht klar dargestellt, du musst 'va_arg' nicht im selben Umfang wie' va_start' aufrufen. Ich habe meine Aussage geklärt. – Motti

8

Es gibt keinen direkten Weg für eine variadische Funktion, um zu bestimmen, wie viele Argumente übergeben wurden. (Zumindest gibt es keinen tragbaren Weg; die <stdarg.h> Schnittstelle liefert diese Information nicht.)

Es gibt mehrere indirekte Möglichkeiten.

Zwei der häufigsten sind:

  • Ein Formatstring (die angibt, über was Sie eine kleine einfache Sprache nennen könnte, die Zahl und die Art (en) der übrigen Argumente). Die Funktionsfamilien *printf() und *scanf() verwenden diesen Mechanismus.
  • Ein Sentinel-Wert, der das Ende der Argumente angibt. Einige der Unix/POSIX exec*() Familie von Funktionen tun dies, mit einem Null-Zeiger, um das Ende der Argumente zu markieren.

Aber es gibt auch andere Möglichkeiten:

  • Einfacher gesagt, eine ganze Zahl Zahl, die die Anzahl der folgenden Argumente spezifiziert; vermutlich wären sie in diesem Fall alle vom selben Typ.
  • Abwechselnde Argumente, wobei ein Argument ein Aufzählungswert sein kann, der den Typ des folgenden Arguments angibt.Ein hypothetisches Beispiel könnte wie folgt aussehen:
    func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
    oder sogar:
    func("-i", 42, "-s", "foo", "-d", 1.25, "");
    , wenn Sie die Art und Weise Argumente emulieren wollen typischerweise übergeben werden, um Unix-Befehle.

Man könnte sogar einen Wert einer globalen Variablen zugewiesen werden die Anzahl der Argumente angeben:

func_arg_count = 3; 
func(1, 2, 3); 

, die hässlich wäre aber völlig legal.

Bei all diesen Techniken ist der Anrufer ausschließlich dafür verantwortlich, konsistente Argumente zu übergeben. der Angerufene kann nur annehmen, dass seine Parameter korrekt sind.

Beachten Sie, dass eine Variadic-Funktion nicht erforderlich ist, um alle übergebenen Argumente zu verarbeiten. Zum Beispiel diese:

printf("%d\n", 10, 20); 

wird 10 drucken und leise die 20 ignorieren. Es gibt kaum einen Grund, diese Funktion zu nutzen.

10

Ein Ansatz, der noch nicht erwähnt wurde, ist die Verwendung eines Vorprozessor-Makros, um die Variadikt-Funktion unter Verwendung der va_list-Länge als ersten Parameter aufzurufen und auch entlang der Argumente weiterzuleiten. Dies ist eine etwas "süße" Lösung, erfordert jedoch keine manuelle Eingabe der Länge der Argumentliste.

Angenommen, Sie die folgende Funktion haben:

int Min(int count, ...) { 
    va_list args; 
    va_start(args, count); 

    int min = va_arg(args, int); 
    for (int i = 0; i < count-1; ++i) { 
     int next = va_arg(args, int); 
     min = min < next ? min : next; 
    } 
    va_end(args); 

    return min; 
} 

Die Idee ist, dass Sie einen Präprozessormakro für die __VA_ARGS__ unter Verwendung einer Maske, die Anzahl der Argumente des Zählens der Lage haben. Es gibt ein paar gute Präprozessor Bibliotheken für die Bestimmung der __VA_ARGS__ Länge einschließlich P99 und Preprocessor steigern, aber nur so verlasse ich keine Löcher in dieser Antwort, hier ist, wie es getan werden kann:

#define IS_MSVC _MSC_VER && !__INTEL_COMPILER 

/** 
* Define the macros to determine variadic argument lengths up to 20 arguments. The MSVC 
* preprocessor handles variadic arguments a bit differently than the GNU preprocessor, 
* so we account for that here. 
*/ 
#if IS_MSVC 
    #define MSVC_HACK(FUNC, ARGS) FUNC ARGS 
    #define APPLY(FUNC, ...) MSVC_HACK(FUNC, (__VA_ARGS__)) 
    #define VA_LENGTH(...) APPLY(VA_LENGTH_, 0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 
#else 
    #define VA_LENGTH(...) VA_LENGTH_(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) 
#endif 

/** 
* Strip the processed arguments to a length variable. 
*/ 
#define VA_LENGTH_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N 

Hinweis: A Viel Lärm von oben ist die Unterstützung von MSVC.

Mit dem oben definiert sind, können Sie ein einzelnes Makro erstellen, um alle Länge basierte Operationen auszuführen:

/** 
* Use the VA_LENGTH macro to determine the length of the variadict args to 
* pass in as the first parameter, and forward along the arguments after that. 
*/ 
#define ExecVF(Func, ...) Func(VA_LENGTH(__VA_ARGS__), __VA_ARGS__) 

Dieses Makro ist der Aufruf jede variadict Funktion so lange der Lage, wie es mit dem int count Parameter beginnt. Kurz gesagt, statt mit:

int result = Min(5, 1, 2, 3, 4, 5); 

können Sie verwenden:

int result = ExecVF(Min, 1, 2, 3, 4, 5); 

Hier ist eine Template-Version von Min, die den gleichen Ansatz verwendet: https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca

+2

Sehr niedlich. In Zeile 3 von Min, sollte es "count" statt "value" heißen? – Amoss

+0

Aktualisiert - Vielen Dank, dass Sie darauf hingewiesen haben! – mbolt35

2

Sie können versuchen, Funktion verwenden _vscprintf wenn Sie arbeiten unter MS Visual Studio. Hier ist ein Beispiel, wie _vscprintf verwendet wird, ich habe es verwendet, um zu wissen, wieviel Platz ich für meinen Konsolentitel für malloc benötige.

int SetTitle(const char *format,...){ 
    char *string; 
    va_list arguments; 

    va_start(arguments,format); 
     string=(char *)malloc(sizeof(char)*(_vscprintf(format,arguments)+1)); 
     if(string==NULL) 
      SetConsoleTitle("Untitled"); 
     else 
      vsprintf(string,format,arguments); 
    va_end(arguments); 

    if(string==NULL) 
     return SETTITLE_MALLOCFAILED; 
    SetConsoleTitle(string); 
    free(string); 
    return 0; 
} 

Oder Sie können dies tun, Ausgabe in temporäre Datei hinzufügen und lesen dann Daten von ihm zugewiesenen Speicher, wie ich in diesem nächsten Beispiel tat

void r_text(const char *format, ...){ 
    FILE *tmp = tmpfile(); 
    va_list vl; 
    int len; 
    char *str; 

    va_start(vl, format); 
     len = vfprintf(tmp, format, vl); 
    va_end(vl); 
    rewind(tmp); 
    str = (char *) malloc(sizeof(char) * len +1); 
    fgets(str, len+1, tmp); 
    printf("%s",str); 
    free(str); 
    fclose(tmp); 
} 
+0

Es wäre besser, wenn Sie einen relevanten Code zur Verfügung stellen, der zeigt, wie Ihr Vorschlag das Problem tatsächlich löst. Sie können sich auf die anderen Antworten als Richtlinie beziehen. – MasterAM

+0

@MasterAM Meine Antwort wurde aktualisiert – Ruza

+0

@MasterAM Ein weiteres Beispiel hinzugefügt – Ruza

Verwandte Themen