2010-07-17 27 views
2

Disons que j'ai une fonction variadique foo(int tmp, ...), en appelant la fonction foo j'ai besoin de savoir combien d'arguments il y a. Je suis au courant de deux façons de savoir combien d'arguments il y a:Utilise des fonctions variadiques dans C89 sans passer le nombre d'arguments ou un argument final?

  1. Utilisez un dernier argument lors de l'appel foo, comme -1, de sorte que votre appel de fonction sera comme ceci: foo(tmp, 1, 2, 9, -1) et quand vous êtes à l'intérieur foo et un appel va_arg retourne -1 vous savez que vous avez lu tous les arguments de la fonction

  2. Ajouter un argument de plus dans foo où le programmeur aura le nombre total d'arguments, de sorte que vous aurez des appels à foo comme ceci: foo(tmp, 5, 1, 2, 3, 4, 5) ou foo(tmp, 2, 7, 8)

J'avais l'habitude de suivre la première voie et une fois eu le bug suivant. Avec le code:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e, -1) 

où expr_of_type était une fonction variadique et vérifiait si expr (le premier argument) est l'un des types suivants (ou boolexpr_e new_table_e ou nil_e avait tout type d'un type énuméré). Je un mégarde écrit:

expr_of_type(expr, boolexpr_e, newtable_e, nil_e -1) 

J'ai oublié la virgule entre nil_e et -1, car nil_e avait un type énuméré, nil_e - 1 était une expression valide et parce que nil_e n'était pas la fonction 0 variadique donnée lorsque vous essayez de Obtenir les arguments expr_of_type n'a pas trouvé -1 comme dernier argument et a continué la recherche en créant un bug qui m'a pris du temps pour le découvrir.

Je ne trouve pas non plus la deuxième manière sympa, car lorsque vous ajoutez ou supprimez un argument de plus d'une fonction variadique, vous devez changer le paramètre qui contient le nombre total d'arguments. Dans la recherche d'une meilleure façon d'utiliser/créer des fonctions variadiques, j'ai trouvé variadic macros qui peut résoudre le bug que j'avais lors de l'utilisation de la première façon. Mais les macros variées sont disponibles pour la norme C99. Je cherchais un meilleur moyen d'utiliser/créer des fonctions variadiques en C89. Des idées?

Répondre

6

En général, vous devez toujours transmettre le nombre d'arguments, que ce soit via une valeur sentinelle ou via un compte explicite.

Vous pourriez, cependant, résoudre votre problème sentinelle en faisant une meilleure sentinelle. Ceci est l'une des raisons pour lesquelles les macros préprocesseur qui se dilatent à des constantes négatives doivent être entourées entre parenthèses:

#define VARARG_SENTINEL (-1) 

Ensuite nil_e VARARG_SENTINEL va générer une erreur de compilation.

En utilisant enum ou const int fonctionnerait aussi:

enum { VARARG_SENTINEL = -1 }; 

En utilisant une constante symbolique pour la valeur sentinelle serait mieux pour d'autres raisons (auto-documentera, plus facile de changer la valeur sous-jacente plus tard).

+0

L'utilisation de 'enum' ou' const int' entraînerait le même problème lors de l'oubli d'une virgule? –

+1

Non, ce ne sera pas le cas. Si vous avez 'const int VARARG_SENTILEL = -1;' quand vous oubliez une virgule, vous aurez quelque chose comme ceci 'nil_e VARARG_SENTINEL' qui donne une erreur de compilation. La même chose se produit lorsque vous utilisez 'enum {VARARG_SENTINEL = -1};'. Avec les deux solutions lorsque vous oubliez une virgule, c'est comme si vous aviez 'number1 number2' (par exemple' 2 3') qui est une expression invalide, mais en utilisant -1 comme je le faisais, vous auriez 'number - 1' qui est un expression valide. –

0

Il est également possible d'éviter complètement les paramètres variadiques en utilisant une structure dynamique.

struct vararray { 
    uint_t n; 
    uint_t params[0]; 
}; 

void foo(int tmp, struct varray *pVA); 

peut même être complexifié avec un union de struct de différentes tailles.

Nous avions une fois un contrôleur intégré avec une API spécifique où nous avons utilisé ce type d'approche, un union de taille fixe struct qui a été transmis au gestionnaire d'événements. Il présente certains avantages car des types spécifiques peuvent être utilisés et le compilateur peut mieux vérifier les types de paramètres de fonction car il ne faut pas oublier qu'il n'y a pas de vérification de type de paramètre sur les fonctions variadiques.

0

Si votre compilation C99, vous pouvez utiliser des macros variadique pour fournir des arguments variables sans avoir à passer le compte explicitement:

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

void _foo(size_t n, int xs[]) 
{ 
    for(int i=0 ; i < n ; i++) { 
     int x = xs[i]; 
     printf("%d\n", x); 
    }   
} 

#define foo(arg1, ...) do {   \ 
    int _x[] = { arg1, __VA_ARGS__ }; \ 
    _foo(sizeof(_x)/sizeof(_x[0]), _x); \ 
} while(0) 

int main() 
{ 
    foo(1, 2, 3, 4); 
    foo(-1, -2, -3, -4, -5, -6, -7); 
    return 0; 
} 

Sortie:

1 
2 
3 
4 
-1 
-2 
-3 
-4 
-5 
-6 
-7 

Cela vous empêche de retourner une valeur, toutefois. Vous pouvez retourner une valeur avec des extensions gcc:

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

int _foo(size_t n, int xs[]) 
{ 
    int i; 
    for(i=0 ; i < n ; i++) { 
     int x = xs[i]; 
     printf("%d\n", x); 
    }   
    return n; 
} 

#define foo(arg1, ...) ({    \ 
    int _x[] = { arg1, __VA_ARGS__ }; \ 
    _foo(sizeof(_x)/sizeof(_x[0]), _x); \ 
}) 

int main() 
{ 
    int x = foo(1, 2, 3, 4); 
    printf("foo returned %d\n", x); 
    x = foo(-1, -2, -3, -4, -5, -6, -7); 
    printf("foo returned %d\n", x); 
    return 0; 
} 

Sortie:

1 
2 
3 
4 
foo returned 4 
-1 
-2 
-3 
-4 
-5 
-6 
-7 
foo returned 7 

Mais, bien sûr, les macros sont morts. Vive les macros!

EDIT:

Oops, n'a pas lu l'OP avec suffisamment d'attention. Pardon!