Un exemple d’utilisation de varargs en C

Ici, j’ai trouvé un exemple d’utilisation de varargs en C.

#include  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 vraiment pourquoi nous utilisons va_start(ap, count); . Autant que je sache, nous avons ainsi défini l’iterator sur son premier élément. Mais pourquoi n’est-il pas réglé au début par défaut?

  2. Je ne comprends pas pourquoi nous devons donner le 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? Cela place-t-il l’iterator à la fin de la liste? Mais n’est-il pas placé à 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?

Rappelez-vous que les arguments sont passés sur la stack. La fonction va_start contient le code “magique” pour initialiser la liste va_list avec le bon pointeur de stack. Le dernier argument nommé dans la déclaration de fonction doit lui être transmis, sinon cela ne fonctionnera pas.

va_arg utilise ce pointeur de stack enregistré et extrait le nombre d’octets correct pour le type fourni, puis modifie ap pour qu’il pointe vers le prochain argument de la stack.


En réalité, ces fonctions ( va_start , va_arg et va_end ) ne sont pas réellement 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 stack et la façon dont il pousse des arguments sur la stack.

Mais pourquoi n’est-il pas réglé au début par défaut?

Peut-être pour des raisons historiques, quand les compilateurs n’étaient pas assez intelligents. Peut-être parce que vous avez un prototype de fonction varargs qui ne se soucie pas vraiment de varargs et que la configuration de varargs s’avère coûteuse pour ce système particulier. Peut-être à cause d’opérations plus complexes dans lesquelles vous va_copy ou souhaitez-vous recommencer à utiliser les arguments plusieurs fois et à appeler va_start plusieurs fois.

La version courte est: parce que le standard de langue le dit.

Deuxièmement, je ne comprends pas 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 qui count . C’est le dernier argument nommé de la fonction. va_start besoin pour savoir où sont les varargs. C’est probablement pour des raisons historiques sur d’anciens compilateurs. Je ne vois pas pourquoi cela ne pourrait pas être mis en œuvre différemment aujourd’hui.

Deuxième partie de votre question: non, le compilateur ne sait pas combien d’arguments ont été envoyés à la fonction. Il se peut même qu’il ne se trouve pas dans la même unité de compilation ni 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 telle que printf . Lorsque vous comstackz votre bibliothèque, le compilateur ne sait pas quand et comment les programmes vont appeler printf . Sur la plupart des ABI (ABI correspond aux conventions régissant l’appel des fonctions, la manière dont les arguments sont transmis, etc.), il est impossible de déterminer le nombre d’arguments qu’un appel à une fonction a reçu. Il est inutile d’inclure cette information dans un appel de fonction et ce n’est presque jamais nécessaire. Vous devez donc avoir un moyen de dire à la fonction varargs le nombre d’arguments qu’elle a reçus. Accéder à va_arg au-delà du nombre d’arguments réellement transmis constitue un comportement indéfini.

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

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

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

va_start initialise la liste des arguments variables. Vous passez toujours le dernier argument de la fonction nommée en tant que second paramètre. C’est parce que vous devez fournir des informations sur l’emplacement dans la stack, où les arguments de variables commencent, car les arguments sont placés sur la stack et le compilateur ne peut pas savoir où se trouve le début de la liste d’arguments de 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 de va_start.

C’est C macroses. va_start définit le pointeur interne sur l’adresse du premier élément. va_end nettoyage va_list . S’il y a va_start dans le code et qu’il n’y a pas de va_end , c’est UB.

Les ressortingctions que l’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 situé le plus à droite dans la liste de parameters variables de la définition de fonction (celle située 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 incompatible avec le type résultant lors de la transmission d’un argument pour lequel il n’y a pas de paramètre, le comportement est indéfini.