Länge von va_list bei Verwendung variabler Listenargumente?

Lesezeit: 9 Minuten

Benutzeravatar von Anton Kazennikov
Anton Kasennikow

Gibt es eine Möglichkeit, die Länge zu berechnen? va_list? In allen Beispielen, die ich gesehen habe, ist die Anzahl der variablen Parameter explizit angegeben.

  • Jede Implementierung von va_arg dass ich gesehen habe, bewegt einfach den Zeiger sizeof(arg_type) Bytes, also hat es keine Möglichkeit, die Länge zu kennen; das ist dein Job.

    – Ed S.

    3. September 2013 um 5:22 Uhr

  • +1 sehr interessante Frage … Ich habe einen Weg auf meinem Compiler gefunden, aber es ist ein böser schmutziger Asm-Trick. Siehe meine Antwort, wenn Sie interessiert sind.

    – Spektre

    13. Juni 2018 um 13:11 Uhr

  • Übrigens, 2022, wenn Sie C++ verwenden, dann nicht Verwenden Sie var-Argumente. Verwenden Parameterpakete. Der Link hat ein schönes Beispiel für eine printf-ähnliche Funktion.

    – Duthomhas

    11. September um 6:31 Uhr

Benutzeravatar von Motti
Motti

Es gibt keine Möglichkeit, die Länge von a zu berechnen va_listdeshalb brauchen Sie den Formatstring in printf wie Funktionen.

Das einzige Funktionen verfügbare Makros für die Arbeit mit a va_list sind:

  • va_start – Beginnen Sie mit der Verwendung der va_list
  • va_arg – Holen Sie sich das nächste Argument
  • va_end – Verwenden Sie die nicht mehr va_list
  • va_copy (seit C++11 und C99) – kopieren Sie die va_list

Bitte beachten Sie, dass Sie anrufen müssen va_start und va_end im selben Gültigkeitsbereich, was bedeutet, dass Sie es nicht in eine aufrufende Utility-Klasse einschließen können va_start in seinem Konstruktor und va_end in seinem Destruktor (ich wurde einmal davon gebissen).

Zum Beispiel ist diese Klasse 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 aus folgender Fehler

t.cpp: Im Konstruktor arg_list::arg_list(const int&):
Zeile 7: Fehler: va_start in Funktion mit festen Argumenten verwendet
Kompilierung aufgrund von -Wfatal-Fehlern abgebrochen.

  • Hmm? Nein, es ist vollkommen erlaubt anzurufen va_start()dann passieren die va_arg zu einer anderen Funktion, dann aufrufen va_end(). Genau so benutzt man die vsprintf() und ähnliche Funktionen.

    – Café

    8. April 2010 um 7:36 Uhr

  • @caf: In welchem ​​Universum entgeht die va_list-Instanz bei Verwendung von vsprintf() dem Gültigkeitsbereich?

    – Andreas Magnusson

    8. April 2010 um 7:44 Uhr

  • Die andere Alternative ist eine Art Endmarkierung. Ein Beispiel finden Sie unter execl()die beendet wird, sobald ein NULL-Wert gefunden wird.

    – David S

    28. August 2013 um 18:15 Uhr

  • @DaveS: Achten Sie einfach darauf, dass das Übergeben von 0 in einer Vararg-Liste NICHT dasselbe ist wie das Übergeben von NULL. Es wird keine Typkonvertierung durchgeführt, da der Compiler nicht weiß, dass 0 in einen Zeiger übergeht. Dies kann dazu führen, dass eine 32-Bit-Ganzzahl 0 an einen 64-Bit-Zeiger-Slot übergeben wird und Kopfschmerzen verursacht.

    – Zan Luchs

    28. August 2013 um 18:29 Uhr

  • @ZanLynx Und achten Sie darauf, dass das Übergeben von NULL in einer Vararg-Liste NICHT dasselbe ist wie das Übergeben von (void*)0. NULL kann als 0, als 0L, als (void*)0 und auf zahlreiche andere Arten definiert werden.

    Benutzer743382

    9. Februar 2014 um 18:07 Uhr

Benutzeravatar von mbolt35
mbolt35

Ein Ansatz, der noch nicht erwähnt wurde, besteht darin, ein Präprozessor-Makro zu verwenden, um die variadict-Funktion mit der va_list-Länge als erstem 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 haben die folgende Funktion:

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 ein Präprozessormakro haben, das in der Lage ist, die Anzahl der Argumente zu zählen, indem Sie eine Maske für die verwenden __VA_ARGS__. Es gibt ein paar gute Präprozessorbibliotheken zur Bestimmung der __VA_ARGS__ Länge inkl P99 und Boost Preprocessor, aber nur damit ich keine Löcher in dieser Antwort hinterlasse, hier ist, wie es gemacht 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

Notiz: Ein Großteil des Lärms von oben ist Workaround-Unterstützung für MSVC.

Mit der oben definierten Definition können Sie ein einzelnes Makro erstellen, um alle längenbasierten 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 kann jede Variadict-Funktion aufrufen, solange sie mit dem beginnt int count Parameter. Kurz gesagt, anstatt zu verwenden:

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

Sie können Folgendes verwenden:

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

Hier ist eine Vorlagenversion von Min, die denselben Ansatz verwendet: https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca

  • Sehr hübsch. Sollte in Zeile 3 von Min “count” statt “value” stehen?

    – Andreas

    14. Februar 2014 um 7:43 Uhr

Benutzeravatar von Keith Thompson
Keith Thompson

Es gibt kein Direkte Möglichkeit für eine variadische Funktion zu bestimmen, wie viele Argumente übergeben wurden. (Zumindest gibt es keine tragbare Möglichkeit; die <stdarg.h> Die Schnittstelle stellt diese Informationen nicht bereit.)

Es gibt einige indirekt Wege.

Zwei der häufigsten sind:

  • Ein Format-String (der, wie man es vielleicht als kleine einfache Sprache bezeichnen könnte, die Anzahl und Art(en) der verbleibenden Argumente angibt). Das *printf() und *scanf() Familien von Funktionen verwenden diesen Mechanismus.
  • Ein Sentinel-Wert, der das Ende der Argumente angibt. Einige der Unix/POSIX exec*() Familie von Funktionen tun dies, indem sie einen Nullzeiger verwenden, um das Ende der Argumente zu markieren.

Aber es gibt noch andere Möglichkeiten:

  • Einfacher, eine Ganzzahl, die die Anzahl der folgenden Argumente angibt; vermutlich wären sie in diesem Fall alle vom gleichen Typ.
  • Alternative Argumente, wobei ein Argument ein Aufzählungswert sein kann, der den Typ des folgenden Arguments angibt. Ein hypothetisches Beispiel könnte so aussehen:
    func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);

    oder auch:
    func("-i", 42, "-s", "foo", "-d", 1.25, "");

    wenn Sie die Art und Weise emulieren möchten, wie Argumente normalerweise an Unix-Befehle übergeben werden.

Sie könnten sogar einer globalen Variablen einen Wert zuweisen, um die Anzahl der Argumente anzugeben:

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

was hässlich, aber vollkommen legal wäre.

Bei all diesen Techniken liegt es ausschließlich in der Verantwortung des Anrufers, konsistente Argumente zu übergeben; der Angerufene kann nur davon ausgehen dass seine Parameter korrekt sind.

Beachten Sie, dass eine variadische Funktion nicht alle an sie übergebenen Argumente verarbeiten muss. Zum Beispiel dies:

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

wird drucken 10 und ignoriere das ruhig 20. Es gibt selten einen Grund, diese Funktion zu nutzen.

Hmm, wenn Sie keine Angst vor bösen Asm-Hacks haben, können Sie die Aufrufkonvention Ihres Compilers ausnutzen. Dadurch wird Ihr Code jedoch auf bestimmte Plattform-/Compiler-/Aufrufkonventionen beschränkt.

Zum Beispiel im BDS2006 C++ 32bit x86 Windows-App (Ich beziehe mich nur auf diese Plattform) Die Argumente werden auf den Stapel gelegt und dann aufgerufen, und dann wird der Stapelzeigerwert repariert (durch die Größe des verwendeten Stapels), nachdem die Funktion zurückgegeben wurde. Hier kleines Beispiel:

double x;
x=min(10.0,20.0,30.0,40.0,50.0);

Der Aufruf wird folgendermaßen übersetzt:

Unit1.cpp.28: x=min(10.0,20.0,30.0,40.0,50.0);
00401B9C 6800004940       push $40490000
00401BA1 6A00             push $00
00401BA3 6800004440       push $40440000
00401BA8 6A00             push $00
00401BAA 6800003E40       push $403e0000
00401BAF 6A00             push $00
00401BB1 6800003440       push $40340000
00401BB6 6A00             push $00
00401BB8 6800002440       push $40240000
00401BBD 6A00             push $00
00401BBF E894FDFFFF       call min(double,double,????)
00401BC4 83C428           add esp,$28

achte auf die letzte Anweisung nach dem Anruf. das $28 ist die Größe, die von 4 Argumenten und einem Rückgabewert verbraucht wird. Wenn Sie also diesen Wert in Ihrer Funktion lesen können, können Sie die Anzahl der Argumente genau bestimmen (wenn ihre Größe bekannt ist). Also hier funktionierendes Beispiel:

double min(double x,double ...) // = min(x,y)
        {
        int n,dn=sizeof(double);
        asm {
            mov eax,esp // store original stack pointer 
            mov esp,ebp // get to the parrent scope stack pointer
            pop ebx
            pop ebx     // this reads the return address of the call pointing to the first instruction after it which is what we want
            mov esp,eax // restore stack pointer
            sub eax,eax; // just eax=0
            mov al,[ebx+2] // read lowest BYTE of eax with the $28 from the add esp,$28
            mov n,eax // store result to local variable for usage
            }
        n-=dn;  // remove return value  from the count

        double z; z=x;
        va_list va;
        va_start(va,x); n-=dn;
        for (;n>=0;n-=dn)
            {
            x=va_arg(va,double);
            if (z>x) z=x;
            }
        va_end(va);
        return z;
        }

Beachten Sie, dass jeder Compiler eine andere Aufrufsequenz haben kann, also überprüfen Sie zuerst die Assemblerliste während des Debuggens, bevor Sie sie verwenden !!!

Benutzeravatar von Ruza
Ruza

Sie können versuchen, die Funktion zu verwenden _vscprintf wenn Sie unter MS Visual Studio arbeiten. Hier ist ein Beispiel für die Verwendung von _vscprintf. Ich habe es verwendet, um zu wissen, wie viel Speicherplatz ich zum Malloc für meinen Konsolentitel 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, die Ausgabe der temporären Datei hinzufügen und dann Daten daraus in den zugewiesenen Speicher lesen, wie ich es in diesem nächsten Beispiel getan habe:

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

  • Es wäre besser, wenn Sie einen relevanten Code bereitstellen würden, der zeigt, wie Ihr Vorschlag das Problem tatsächlich löst. Sie können sich auf die anderen Antworten als Richtlinie beziehen.

    – MasterAM

    9. Februar 2014 um 18:15 Uhr

  • @MasterAM Meine Antwort wurde aktualisiert

    – Ruza

    18. April 2014 um 20:09 Uhr

  • @MasterAM Ein weiteres Beispiel hinzugefügt

    – Ruza

    14. Mai 2014 um 16:34 Uhr

  • Gießen Sie nicht das Ergebnis von malloc in C und unter Verwendung eines Variablennamens, der einem C++-Typ ähnlich ist, wie string ist eine wirklich schlechte idee

    – phuklv

    3. März 2017 um 5:28 Uhr

Benutzeravatar von Yashwant Patil
Yashwant Patil

Verwenden Sie _vscprintf, um die Länge der Variablenliste zu bestimmen.
https://msdn.microsoft.com/en-us/library/w05tbk72.aspx

  • Es wäre besser, wenn Sie einen relevanten Code bereitstellen würden, der zeigt, wie Ihr Vorschlag das Problem tatsächlich löst. Sie können sich auf die anderen Antworten als Richtlinie beziehen.

    – MasterAM

    9. Februar 2014 um 18:15 Uhr

  • @MasterAM Meine Antwort wurde aktualisiert

    – Ruza

    18. April 2014 um 20:09 Uhr

  • @MasterAM Ein weiteres Beispiel hinzugefügt

    – Ruza

    14. Mai 2014 um 16:34 Uhr

  • Gießen Sie nicht das Ergebnis von malloc in C und unter Verwendung eines Variablennamens, der einem C++-Typ ähnlich ist, wie string ist eine wirklich schlechte idee

    – phuklv

    3. März 2017 um 5:28 Uhr

1398740cookie-checkLänge von va_list bei Verwendung variabler Listenargumente?

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

Privacy policy