Passando un numero variabile di argomenti


333

Supponiamo che io abbia una funzione C che accetta un numero variabile di argomenti: come posso chiamare un'altra funzione che prevede un numero variabile di argomenti dall'interno di essa, passando tutti gli argomenti che sono entrati nella prima funzione?

Esempio:

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
Il tuo esempio mi sembra un po 'strano, in quanto passi fmt a format_string () e a fprintf (). Format_string () dovrebbe restituire una nuova stringa in qualche modo?
Kristopher Johnson,

2
L'esempio non ha senso. Era solo per mostrare lo schema del codice.
Vicent Marti,

162
"Dovrei cercare su Google": Non sono d'accordo. Google ha molto rumore (informazioni poco chiare, spesso confuse). Avere una buona risposta (votata, accettata) su StackOverflow aiuta davvero!
Ansgar,

71
Giusto per ponderare: sono arrivato a questa domanda da google, e poiché si trattava di overflow dello stack, ero molto fiducioso che la risposta sarebbe stata utile. Quindi chiedi via!
dieci

32
@Ilya: se nessuno avesse mai scritto cose al di fuori di Google, non ci sarebbero informazioni da cercare su Google.
Erik Kaplun,

Risposte:


211

Per passare le ellissi, devi convertirle in una va_list e usare quella va_list nella tua seconda funzione. specificamente;

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
Il codice è tratto dalla domanda ed è in realtà solo un'illustrazione di come convertire le ellissi piuttosto che qualsiasi cosa funzionale. Se lo guardi non format_stringsarà nemmeno utile, poiché dovrebbe apportare modifiche in situ a fmt, che certamente non dovrebbe essere fatto neanche. Le opzioni potrebbero includere la rimozione completa di format_string e l'utilizzo di vfprintf, ma ciò presuppone cosa fa effettivamente format_string o che format_string restituisca una stringa diversa. Modificherò la risposta per mostrare quest'ultima.
SmacL

1
Se la tua stringa di formato utilizza gli stessi comandi di stringa di formato di printf, puoi anche ottenere alcuni compilatori come gcc e clang per avvisarti se la tua stringa di formato non è compatibile con gli attuali argomenti passati. Vedi l'attributo della funzione GCC ' formato "per maggiori dettagli: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson,

1
Questo non sembra funzionare se passi gli arg due volte di seguito.
fotanus,

2
@fotanus: se si chiama una funzione con argptre la funzione chiamata utilizza argptraffatto, l'unica cosa sicura da fare è chiamare va_end()e quindi riavviare va_start(argptr, fmt);per reinizializzare. Oppure puoi usare va_copy()se il tuo sistema lo supporta (C99 e C11 lo richiedono; C89 / 90 no).
Jonathan Leffler,

1
Si noti che il commento di @ ThomasPadron-McCarthy non è più aggiornato e che la stampa finale è ok.
Federico,

59

Non c'è modo di chiamare (ad es.) Printf senza sapere quanti argomenti stai passando ad esso, a meno che tu non voglia fare brutti scherzi e non portatili.

La soluzione generalmente usata è quella di fornire sempre una forma alternativa di funzioni vararg, quindi printfha una vprintfche prende il va_listposto di .... Le ...versioni sono solo involucri attorno alle va_listversioni.


Si noti che nonvsyslog è conforme POSIX .
patryk.beza,

53

Le funzioni variabili possono essere pericolose . Ecco un trucco più sicuro:

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

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

11
Ancora meglio è questo trucco: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III,

5
@ RichardJ.RossIII Vorrei che tu espandessi sul tuo commento, è difficilmente leggibile in questo modo, non riesco a capire l'idea alla base del codice e in realtà sembra molto interessante e utile.
penelope,

5
@ArtOfWarfare non sono sicuro di essere d'accordo sul fatto che si tratti di un brutto trucco, Rose ha un'ottima soluzione ma implica la digitazione di func ((type []) {val1, val2, 0}); che sembra goffo, dove se avessi #define func_short_cut (...) func ((type []) { VA_ARGS }); allora potresti semplicemente chiamare func_short_cut (1, 2, 3, 4, 0); che ti dà la stessa sintassi di una normale funzione variadica con l'ulteriore vantaggio del trucco di Rose ... qual è il problema qui?
chrispepper1989,

9
Cosa succede se si desidera passare 0 come argomento?
Julian Gold,

1
Ciò richiede che i tuoi utenti ricordino di chiamare con un 0. di chiusura. Come è più sicuro?
cp. Il

29

Nel magnifico C ++ 0x potresti usare modelli variadic:

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

Non dimenticare che i modelli variadic non sono ancora disponibili in Visual Studio ... questo potrebbe ovviamente non interessarti!
Tom Swirly,

1
Se si utilizza Visual Studio, i modelli variadic possono essere aggiunti a Visual Studio 2012 utilizzando il CTP di novembre 2012. Se stai usando Visual Studio 2013, avrai modelli variadic.
user2023370

7

È possibile utilizzare l'assemblaggio in linea per la chiamata di funzione. (in questo codice presumo che gli argomenti siano caratteri).

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
Non portatile, dipende dal compilatore e impedisce l'ottimizzazione del compilatore. Pessima soluzione.
Geoffroy,

4
Nuovo anche senza cancellare.
user7116

8
Almeno questo in realtà risponde alla domanda, senza ridefinire la domanda.
lama12345,

6

Puoi anche provare la macro.

#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

Anche se puoi risolvere il passaggio del formatter memorizzandolo prima nel buffer locale, ma questo ha bisogno di stack e a volte può essere un problema da affrontare. Ho provato a seguire e sembra funzionare bene.

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

Spero che questo ti aiuti.


2

La soluzione di Ross ha ripulito un po '. Funziona solo se tutti gli arg sono puntatori. Anche l'implementazione del linguaggio deve supportare l'eliminazione della virgola precedente se __VA_ARGS__è vuota (sia Visual Studio C ++ che 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

Supponiamo che tu abbia una tipica funzione variadica che hai scritto. Poiché è necessario almeno un argomento prima di quello variadico ..., è necessario scrivere sempre un argomento aggiuntivo durante l'utilizzo.

O tu?

Se si avvolge la funzione variadica in una macro, non è necessario alcun argomento precedente. Considera questo esempio:

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

Questo è ovviamente molto più conveniente, poiché non è necessario specificare l'argomento iniziale ogni volta.


-5

Non sono sicuro che funzioni per tutti i compilatori, ma per me ha funzionato finora.

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

Puoi aggiungere ... a inner_func () se vuoi, ma non ti serve. Funziona perché va_start utilizza l'indirizzo della variabile data come punto iniziale. In questo caso, gli stiamo fornendo un riferimento a una variabile in func (). Quindi usa quell'indirizzo e legge le variabili dopo quella nello stack. La funzione inner_func () sta leggendo l'indirizzo dello stack di func (). Quindi funziona solo se entrambe le funzioni usano lo stesso segmento di stack.

Le macro va_start e va_arg generalmente funzionano se si fornisce loro var come punto di partenza. Quindi, se vuoi, puoi passare i puntatori ad altre funzioni e usare anche quelle. Puoi creare le tue macro abbastanza facilmente. Tutto ciò che fanno le macro è l'indirizzo di memoria del typecast. Tuttavia, farli funzionare per tutti i compilatori e le convenzioni di chiamata è fastidioso. Quindi è generalmente più facile usare quelli forniti con il compilatore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.