Übergabe einer variablen Anzahl von Argumenten


333

Angenommen, ich habe eine C-Funktion, die eine variable Anzahl von Argumenten akzeptiert: Wie kann ich eine andere Funktion aufrufen, die eine variable Anzahl von Argumenten von innen erwartet und alle Argumente übergibt, die in die erste Funktion eingegangen sind?

Beispiel:

void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }

4
Ihr Beispiel sieht für mich etwas seltsam aus, da Sie fmt sowohl an format_string () als auch an fprintf () übergeben. Sollte format_string () irgendwie einen neuen String zurückgeben?
Kristopher Johnson

2
Beispiel macht keinen Sinn. Es war nur, um den Umriss des Codes zu zeigen.
Vicent Marti

162
"sollte gegoogelt werden": Ich bin anderer Meinung. Google hat viel Lärm (unklare, oft verwirrende Informationen). Es hilft wirklich, eine gute (abgestimmte, akzeptierte) Antwort auf den Stackoverflow zu haben!
Ansgar

71
Nur zum Abwägen: Ich bin auf diese Frage von Google gekommen, und weil es sich um einen Stapelüberlauf handelte, war ich sehr zuversichtlich, dass die Antwort nützlich sein würde. Also frag weg!
Tenpn

32
@Ilya: Wenn niemand außerhalb von Google jemals etwas aufgeschrieben hätte, gäbe es keine Informationen, nach denen bei Google gesucht werden könnte.
Erik Kaplun

Antworten:


211

Um die Ellipsen weiterzugeben, müssen Sie sie in eine va_list konvertieren und diese va_list in Ihrer zweiten Funktion verwenden. Speziell;

void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...) 
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout, "%s",formatted_string);
}

3
Der Code stammt aus der Frage und ist eigentlich nur ein Beispiel dafür, wie Ellipsen konvertiert werden, anstatt irgendetwas Funktionales. Wenn Sie es sich ansehen, format_stringwird es auch kaum nützlich sein, da es in-situ Änderungen an fmt vornehmen müsste, was sicherlich auch nicht getan werden sollte. Zu den Optionen gehört das vollständige Entfernen von format_string und die Verwendung von vfprintf. Dies setzt jedoch voraus, was format_string tatsächlich tut, oder format_string gibt einen anderen String zurück. Ich werde die Antwort bearbeiten, um Letzteres zu zeigen.
SmacL

1
Wenn Ihre Formatzeichenfolge dieselben Formatzeichenfolgenbefehle wie printf verwendet, können Sie auch einige Compiler wie gcc und clang abrufen, um Sie zu warnen, wenn Ihre Formatzeichenfolge nicht mit den tatsächlich übergebenen Argumenten kompatibel ist. Siehe das GCC-Funktionsattribut ' Format 'für weitere Details: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson

1
Dies scheint nicht zu funktionieren, wenn Sie die Argumente zweimal hintereinander übergeben.
Fotanus

2
@fotanus: Wenn Sie eine Funktion mit der aufrufen argptrund die aufgerufene Funktion die argptrüberhaupt verwendet, ist es nur sicher, sie aufzurufen va_end()und dann neu va_start(argptr, fmt);zu starten, um sie neu zu initialisieren. Oder Sie können verwenden, va_copy()wenn Ihr System dies unterstützt (C99 und C11 erfordern dies; C89 / 90 nicht).
Jonathan Leffler

1
Bitte beachten Sie, dass der Kommentar von @ ThomasPadron-McCarthy nicht mehr aktuell ist und der endgültige fprintf in Ordnung ist.
Frederick

59

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 ist es , immer eine alternative Form von Vararg Funktionen bereitstellen, so printfhat vprintfdas ein nimmt va_listanstelle der .... Das... Versionen sind nur Wrapper um die va_listVersionen.


Beachten Sie, dass vsyslogist nicht POSIX - konform.
patryk.beza

53

Variadische Funktionen können gefährlich sein . Hier ist ein sicherer Trick:

   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});

11
Noch besser ist dieser Trick: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III

5
@ RichardJ.RossIII Ich wünschte, Sie würden Ihren Kommentar erweitern, er ist so kaum lesbar, ich kann die Idee hinter dem Code nicht erkennen und er sieht tatsächlich sehr interessant und nützlich aus.
Penelope

5
@ArtOfWarfare Ich bin nicht sicher, ob ich damit einverstanden bin, dass es ein schlechter Hack ist. Rose hat eine großartige Lösung, aber es beinhaltet die Eingabe von func ((Typ []) {val1, val2, 0}); was sich klobig anfühlt, wenn Sie #define func_short_cut (...) func ((type []) { VA_ARGS }) hatten; dann könnten Sie einfach func_short_cut (1, 2, 3, 4, 0) aufrufen; Das gibt Ihnen die gleiche Syntax wie eine normale Variadic-Funktion mit dem zusätzlichen Vorteil von Roses ordentlichem Trick ... was ist das Problem hier?
chrispepper1989

9
Was ist, wenn Sie 0 als Argument übergeben möchten?
Julian Gold

1
Dies erfordert, dass Ihre Benutzer daran denken, mit einer abschließenden 0 anzurufen. Wie ist es sicherer?
cp.engr

29

In großartigem C ++ 0x können Sie verschiedene Vorlagen verwenden:

template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}

Vergessen Sie nicht, dass verschiedene Vorlagen in Visual Studio immer noch nicht verfügbar sind. Dies kann Sie natürlich nicht interessieren!
Tom Swirly

1
Wenn Sie Visual Studio verwenden, können Visual Studio 2012 mithilfe des CTP vom November 2012 verschiedene Vorlagen hinzugefügt werden. Wenn Sie Visual Studio 2013 verwenden, verfügen Sie über verschiedene Vorlagen.
user2023370

7

Sie können die Inline-Assembly für den Funktionsaufruf verwenden. (In diesem Code gehe ich davon aus, dass die Argumente Zeichen sind).

void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }

4
Nicht portabel, hängt vom Compiler ab und verhindert die Compileroptimierung. Sehr schlechte Lösung.
Geoffroy

4
Neu ohne auch zu löschen.
user7116

8
Zumindest beantwortet dies tatsächlich die Frage, ohne die Frage neu zu definieren.
Lama12345

6

Sie können auch Makro versuchen.

#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)

int main()
{
    int x=10;
    DEBUG_PRINT(DBG, "i am x %d\n", x);
    return 0;
}

6

Sie können das Übergeben des Formatierers zwar lösen, indem Sie ihn zuerst im lokalen Puffer speichern, dies muss jedoch gestapelt werden und kann manchmal problematisch sein. Ich habe versucht zu folgen und es scheint gut zu funktionieren.

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

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("\nMDL: %d, TYPE: %s", mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main() 
{
    int x = 3, y = 6;
    showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
    showLog(1, "ERR");
}

Hoffe das hilft.


2

Ross 'Lösung räumte ein bisschen auf. Funktioniert nur, wenn alle Argumente Zeiger sind. Die Sprachimplementierung muss auch das Löschen des vorherigen Kommas unterstützen, wenn __VA_ARGS__es leer ist (sowohl Visual Studio C ++ als auch GCC).

// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

0

Angenommen, Sie haben eine typische Variadic-Funktion, die Sie geschrieben haben. Da vor dem variadischen mindestens ein Argument erforderlich ist ..., müssen Sie bei der Verwendung immer ein zusätzliches Argument schreiben.

Oder tust du?

Wenn Sie Ihre Variadic-Funktion in ein Makro einschließen, benötigen Sie kein vorhergehendes Argument. Betrachten Sie dieses Beispiel:

#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

Dies ist offensichtlich weitaus praktischer, da Sie nicht jedes Mal das ursprüngliche Argument angeben müssen.


-5

Ich bin mir nicht sicher, ob dies für alle Compiler funktioniert, aber es hat bisher für mich funktioniert.

void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

Sie können das ... zu inner_func () hinzufügen, wenn Sie möchten, aber Sie brauchen es nicht. Dies funktioniert, weil va_start die Adresse der angegebenen Variablen als Startpunkt verwendet. In diesem Fall geben wir einen Verweis auf eine Variable in func (). Es verwendet also diese Adresse und liest die Variablen danach auf dem Stapel. Die Funktion inner_func () liest aus der Stapeladresse von func (). Es funktioniert also nur, wenn beide Funktionen dasselbe Stapelsegment verwenden.

Die Makros va_start und va_arg funktionieren im Allgemeinen, wenn Sie ihnen eine var als Ausgangspunkt geben. Wenn Sie möchten, können Sie Zeiger auf andere Funktionen übergeben und diese auch verwenden. Sie können ganz einfach Ihre eigenen Makros erstellen. Die Makros müssen lediglich Speicheradressen typisieren. Es ist jedoch ärgerlich, sie für alle Compiler und Aufrufkonventionen zum Laufen zu bringen. Daher ist es im Allgemeinen einfacher, diejenigen zu verwenden, die mit dem Compiler geliefert werden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.