Passer un nombre variable d'arguments autour


333

Disons que j'ai une fonction C qui prend un nombre variable d'arguments: Comment puis-je appeler une autre fonction qui attend un nombre variable d'arguments de l'intérieur, en passant tous les arguments qui sont entrés dans la première fonction?

Exemple:

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
Votre exemple me semble un peu bizarre, en ce sens que vous passez fmt à la fois à format_string () et à fprintf (). Format_string () devrait-il retourner une nouvelle chaîne d'une manière ou d'une autre?
Kristopher Johnson

2
L'exemple n'a pas de sens. C'était juste pour montrer le contour du code.
Vicent Marti

162
"devrait être googlé": je ne suis pas d'accord. Google a beaucoup de bruit (informations peu claires, souvent déroutantes). Avoir une bonne réponse (votée, acceptée) sur stackoverflow aide vraiment!
Ansgar

71
Juste pour peser: je suis venu à cette question de Google, et parce que c'était un débordement de pile, j'étais très confiant que la réponse serait utile. Alors demandez loin!
tenpn

32
@Ilya: si personne n'a jamais noté des choses en dehors de Google, il n'y aurait aucune information à rechercher sur Google.
Erik Kaplun

Réponses:


211

Pour transmettre les ellipses, vous devez les convertir en va_list et utiliser cette va_list dans votre deuxième fonction. Plus précisément;

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
Le code est tiré de la question et n'est vraiment qu'une illustration de la façon de convertir des ellipses plutôt que quoi que ce soit de fonctionnel. Si vous le regardez, cela format_stringne sera guère utile non plus, car il faudrait apporter des modifications in situ à fmt, ce qui ne devrait certainement pas être fait non plus. Les options incluent de se débarrasser complètement de format_string et d'utiliser vfprintf, mais cela fait des hypothèses sur ce que format_string fait réellement, ou que format_string renvoie une chaîne différente. Je vais modifier la réponse pour montrer ce dernier.
SmacL

1
Si votre chaîne de format utilise les mêmes commandes de chaîne de format que printf, vous pouvez également obtenir des compilateurs comme gcc et clang pour vous avertir si votre chaîne de format n'est pas compatible avec les arguments réels transmis. Voir l'attribut de fonction GCC ' format 'pour plus de détails: gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html .
Doug Richardson,

1
Cela ne semble pas fonctionner si vous passez les arguments deux fois de suite.
fotanus

2
@fotanus: si vous appelez une fonction avec argptret que la fonction appelée utilise du argptrtout, la seule chose sûre à faire est d'appeler va_end()puis de redémarrer va_start(argptr, fmt);pour réinitialiser. Ou vous pouvez l'utiliser va_copy()si votre système le prend en charge (C99 et C11 l'exigent; C89 / 90 ne l'a pas fait).
Jonathan Leffler

1
Veuillez noter que le commentaire de @ ThomasPadron-McCarthy est désormais obsolète et que le fprintf final est correct.
Frederick

59

Il n'y a aucun moyen d'appeler (par exemple) printf sans savoir combien d'arguments vous lui passez, à moins que vous ne vouliez entrer dans des astuces coquines et non portables.

La solution généralement utilisée est de toujours fournir une forme alternative de fonctions vararg, donc printfa vprintfqui prend la va_listplace de .... Les ...versions ne sont que des enveloppes autour des va_listversions.


Notez que ce vsyslogn'est pas compatible POSIX .
patryk.beza

53

Les fonctions variadiques peuvent être dangereuses . Voici une astuce plus sûre:

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

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

11
Encore mieux est cette astuce: #define callVardicMethodSafely(values...) ({ values *v = { values }; _actualFunction(values, sizeof(v) / sizeof(*v)); })
Richard J. Ross III

5
@ RichardJ.RossIII Je souhaite que vous développiez votre commentaire, il est à peine lisible comme ceci, je ne peux pas comprendre l'idée derrière le code et il semble en fait très intéressant et utile.
penelope

5
@ArtOfWarfare je ne suis pas sûr d'être d'accord que c'est un mauvais hack, Rose a une excellente solution mais cela implique de taper func ((type []) {val1, val2, 0}); ce qui semble maladroit, alors que si vous aviez #define func_short_cut (...) func ((type []) { VA_ARGS }); alors vous pourriez simplement appeler func_short_cut (1, 2, 3, 4, 0); qui vous donne la même syntaxe qu'une fonction variadique normale avec l'avantage supplémentaire de l'astuce soignée de Rose ... quel est le problème ici?
chrispepper1989

9
Et si vous voulez passer 0 comme argument?
Julian Gold

1
Pour cela, vos utilisateurs doivent se rappeler d'appeler avec un 0 de fin. Comment est-ce plus sûr?
cp.engr

29

En magnifique C ++ 0x, vous pouvez utiliser des modèles variadiques:

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

N'oubliez pas que les modèles variadic ne sont toujours pas disponibles dans Visual Studio ... cela pourrait bien ne pas vous préoccuper bien sûr!
Tom Swirly

1
Si vous utilisez Visual Studio, des modèles variadic peuvent être ajoutés à Visual Studio 2012 à l'aide du CTP de novembre 2012. Si vous utilisez Visual Studio 2013, vous aurez des modèles variadic.
user2023370

7

Vous pouvez utiliser l'assemblage en ligne pour l'appel de fonction. (dans ce code, je suppose que les arguments sont des caractères).

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 portable, dépend du compilateur et empêche l'optimisation du compilateur. Très mauvaise solution.
Geoffroy

4
Nouveau sans supprimer aussi.
user7116

8
Au moins, cela répond en fait à la question, sans redéfinir la question.
lama12345

6

Vous pouvez également essayer 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

Bien que vous puissiez résoudre le passage du formateur en le stockant d'abord dans le tampon local, mais cela nécessite une pile et peut parfois être un problème à traiter. J'ai essayé de suivre et cela semble bien fonctionner.

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

J'espère que cela t'aides.


2

La solution de Ross s'est un peu nettoyée. Fonctionne uniquement si tous les arguments sont des pointeurs. L'implémentation du langage doit également prendre en charge la suppression de la virgule précédente si __VA_ARGS__est vide (Visual Studio C ++ et GCC le font).

// 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

Supposons que vous ayez une fonction variadique typique que vous avez écrite. Parce qu'au moins un argument est requis avant le variadic ..., vous devez toujours écrire un argument supplémentaire lors de l'utilisation.

Ou vous?

Si vous encapsulez votre fonction variadique dans une macro, vous n'avez pas besoin d'argument précédent. Considérez cet exemple:

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

C'est évidemment beaucoup plus pratique, car vous n'avez pas besoin de spécifier l'argument initial à chaque fois.


-5

Je ne sais pas si cela fonctionne pour tous les compilateurs, mais cela a fonctionné jusqu'à présent pour moi.

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

Vous pouvez ajouter le ... à inner_func () si vous le souhaitez, mais vous n'en avez pas besoin. Cela fonctionne car va_start utilise l'adresse de la variable donnée comme point de départ. Dans ce cas, nous lui donnons une référence à une variable dans func (). Il utilise donc cette adresse et lit les variables après cela sur la pile. La fonction inner_func () lit à partir de l'adresse de pile de func (). Cela ne fonctionne donc que si les deux fonctions utilisent le même segment de pile.

Les macros va_start et va_arg fonctionneront généralement si vous leur donnez un var comme point de départ. Donc, si vous le souhaitez, vous pouvez passer des pointeurs vers d'autres fonctions et les utiliser également. Vous pouvez créer vos propres macros assez facilement. Les macros ne font que taper des adresses mémoire. Cependant, les faire fonctionner pour tous les compilateurs et les conventions d'appel est ennuyeux. Il est donc généralement plus facile d'utiliser ceux fournis avec le compilateur.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.