web-dev-qa-db-fra.com

Un exemple d'utilisation de varargs en C

Ici J'ai trouvé un exemple de la façon dont les varargs peuvent être utilisés en C.

#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

Je ne peux comprendre cet exemple que dans une certaine mesure.

  1. Je ne comprends pas pourquoi nous utilisons va_start(ap, count);. Autant que je sache, de cette façon, nous mettons l'itérateur à son premier élément. Mais pourquoi n'est-il pas défini au début par défaut?

  2. Je ne comprends pas pourquoi nous devons donner count comme argument. C ne peut-il pas déterminer automatiquement le nombre d'arguments?

  3. Je ne comprends pas pourquoi nous utilisons va_end(ap). Qu'est-ce que ça change? Définit-il l'itérateur à la fin de la liste? Mais n'est-il pas mis à la fin de la liste par la boucle? De plus, pourquoi en avons-nous besoin? Nous n'utilisons plus ap; pourquoi voulons-nous le changer?

34
Roman

N'oubliez pas que les arguments sont transmis sur la pile. Le va_start la fonction contient le code "magique" pour initialiser le va_list avec le pointeur de pile correct. Il doit être passé le dernier argument nommé dans la déclaration de fonction ou cela ne fonctionnera pas.

Quoi va_arg fait est d'utiliser ce pointeur de pile enregistré, d'extraire la quantité correcte d'octets pour le type fourni, puis de modifier ap pour qu'il pointe vers l'argument suivant sur la pile.


En réalité, ces fonctions (va_start, va_arg et va_end) ne sont pas en fait des fonctions, mais implémentées en tant que macros de préprocesseur. L'implémentation réelle dépend également du compilateur, car différents compilateurs peuvent avoir une disposition différente de la pile et la façon dont elle pousse les arguments sur la pile.

34

Mais pourquoi n'est-il pas défini au début par défaut?

Peut-être pour des raisons historiques à partir du moment où les compilateurs n'étaient pas assez intelligents. Peut-être parce que vous pourriez avoir un prototype de fonction varargs qui ne se soucie pas réellement des varargs et que la configuration de varargs s'avère coûteuse sur ce système particulier. Peut-être à cause d'opérations plus complexes où vous faites va_copy ou vous souhaitez peut-être recommencer à travailler avec les arguments plusieurs fois et appeler va_start plusieurs fois.

La version courte est: parce que la norme linguistique le dit.

Deuxièmement, il n'est pas clair pour moi pourquoi nous devons donner le compte comme argument. C++ ne peut-il pas déterminer automatiquement le nombre d'arguments?

Ce n'est pas tout ce que count est. Il s'agit du dernier argument nommé de la fonction. va_start en a besoin pour savoir où sont les varargs. C'est probablement pour des raisons historiques sur les anciens compilateurs. Je ne vois pas pourquoi il ne pourrait pas être mis en œuvre différemment aujourd'hui.

Comme deuxième partie de votre question: non, le compilateur ne sait pas combien d'arguments ont été envoyés à la fonction. Il pourrait même ne pas être dans la même unité de compilation ou même dans le même programme et le compilateur ne sait pas comment la fonction sera appelée. Imaginez une bibliothèque avec une fonction varargs comme printf. Lorsque vous compilez votre libc, le compilateur ne sait pas quand et comment les programmes appellent printf. Sur la plupart des ABI (ABI correspond aux conventions sur la façon dont les fonctions sont appelées, comment les arguments sont passés, etc.), il n'y a aucun moyen de savoir combien d'arguments un appel de fonction a obtenu. Il est inutile d'inclure ces informations dans un appel de fonction et ce n'est presque jamais nécessaire. Vous devez donc avoir un moyen de dire à la fonction varargs combien d'arguments elle a obtenus. Accès à va_arg au-delà du nombre d'arguments réellement passés est un comportement indéfini.

Ensuite, il n'est pas clair pour moi pourquoi utilisons-nous va_end (ap). Qu'est-ce que ça change?

Sur la plupart des architectures va_end ne fait rien de pertinent. Mais il existe des architectures avec des arguments complexes passant la sémantique et va_start pourrait même potentiellement de la mémoire malloc alors vous auriez besoin de va_end pour libérer cette mémoire.

La version courte ici est aussi: parce que la norme linguistique le dit.

6
Art

va_start initialise la liste des arguments variables. Vous passez toujours le dernier argument de la fonction nommée comme deuxième paramètre. C'est parce que vous devez fournir des informations sur l'emplacement dans la pile, où les arguments variables commencent, car les arguments sont poussés sur la pile et le compilateur ne peut pas savoir où il y a un début de liste d'arguments variables (il n'y a pas de différenciation).

Quant à va_end, il est utilisé pour libérer les ressources allouées à la liste d'arguments variables lors de l'appel va_start.

6
W.B.

C'est C macroses. va_start définit le pointeur interne sur l'adresse du premier élément. va_end nettoyer va_list. S'il y a va_start dans le code et il n'y a pas va_end - c'est UB.

Les restrictions que ISO C place sur le deuxième paramètre de la macro va_start () dans l'en-tête sont différentes dans la présente Norme internationale. Le paramètre parmN est l'identifiant du paramètre le plus à droite dans la liste des paramètres variables de la définition de la fonction (celle juste avant le ...). Si le paramètre parmN est déclaré avec une fonction, un tableau ou un type de référence, ou avec un type qui n'est pas compatible avec le type qui en résulte lors du passage d'un argument pour lequel il n'y a pas de paramètre, le comportement n'est pas défini.

3
ForEveR