Fonctions variadiques et constantes

Comment les fonctions variadiques traitent-elles exactement les constantes numériques? Par exemple, considérons le code suivant:

myfunc(5, 0, 1, 2, 3, 4); 

La fonction ressemble à ceci:

 void myfunc(int count, ...) { } 

Maintenant, pour pouvoir parcourir les arguments simples avec va_arg , j’ai besoin de connaître leur taille, par exemple, int , short , char , float , etc. Mais quelle taille dois-je prendre pour les constantes numériques telles que celles que j’utilise dans le code ci-dessus?

Des tests ont montré que le simple fait de supposer que int pour eux semble bien fonctionner ainsi le compilateur semble les pousser comme étant int même si ces constantes pourraient également être représentées dans un seul caractère ou short chacune.

Néanmoins, je cherche une explication au comportement que je constate. Quel est le type standard en C pour passer des constantes numériques à des fonctions variadiques? Est-ce clairement défini ou dépend-il du compilateur? Existe-t-il une différence entre l’architecture 32 bits et l’architecture 64 bits?

Merci!

J’aime la réponse de Jonathan Leffler , mais je pensais avoir quelques détails techniques, pour ceux qui ont l’intention d’écrire une bibliothèque portable ou quelque chose qui fournit une API avec des fonctions variadiques et qui doivent donc approfondir les détails.

Les parameters variés sont soumis aux promotions d’argument par défaut (C11 draft N1570 en format PDF; section 6.5.2.2 Appels de fonction, paragraphe 6):

.. les promotions entières sont effectuées sur chaque argument et les arguments de type float sont promus en double. Celles-ci sont appelées les promotions d’arguments par défaut .

[If] .. les types d’arguments après la promotion ne sont pas compatibles avec ceux des parameters après la promotion, le comportement n’est pas défini, sauf dans les cas suivants:

  • un type promu est un type entier signé, l’autre type promu est le type entier non signé correspondant et la valeur est représentable dans les deux types;

  • les deux types sont des pointeurs vers des versions qualifiées ou non qualifiées d’un type de caractère ou d’un vide

Les constantes à virgule flottante sont de type double , sauf si elles sont suffixées de f ou de F (comme dans 1.0f ), auquel cas elles sont de type float .

En C99 et C11, les constantes entières sont de type int si elles entrent dans un; long (AKA long int ) s’ils correspondent à un autre; de long long (AKA long long int ) sinon. Étant donné que de nombreux compilateurs supposent qu’une constante entière sans suffixe de taille est une erreur humaine ou une faute de frappe, il est recommandé de toujours inclure le suffixe si la constante entière n’est pas de type int .

Les constantes entières peuvent également avoir un suffixe de lettre pour indiquer leur type:

  • u ou U pour unsigned int

  • l ou L pour long int

  • lu ou ul ou LU ou UL ou lU ou Lu ou uL ou Ul pour les commandes unsigned long int

  • ll ou LL ou Ll ou lL pour une long long int

  • llu ou LLU (ou ULL ou n’importe laquelle de leurs variantes majuscule ou minuscule) pour unsigned long long int

Les règles de promotion des nombres entiers figurent à la section 6.3.1.1.

Pour récapituler les règles de promotion des arguments par défaut pour C11 (il y a quelques ajouts par rapport à C89 et C99, mais aucun changement significatif):

  • float sont encouragés à double

  • Tous les types entiers dont les valeurs peuvent être représentées par un int sont promus en int . (Cela inclut à la fois les caractères non signés et signés, ainsi que les champs _Bool et les champs binarys de types _Bool , int et les champs binarys unsigned int plus petits.)

  • Tous les types entiers dont les valeurs peuvent être représentées par un unsigned int (mais pas un int ) sont promus en unsigned int . (Cela inclut les champs de bits unsigned int qui ne peuvent pas être représentés par un int ( CHAR_BIT * sizeof (unsigned int) bits) et les alias typedef’d de unsigned int , mais c’est tout, je pense.)

  • Les types entiers au moins aussi grands que int sont inchangés. Cela inclut les types long / long int , long long / long long int et size_t , par exemple.

Je voudrais signaler qu’il y a un “casse-tête” dans les règles: “signé pour non signé est correct, non signé pour signé est incertain” :

  • Si l’argument est promu en un type entier signé, mais que la fonction obtient la valeur à l’aide du type entier non signé correspondant, la fonction obtient la valeur correcte en utilisant l’arithmétique modulo.

    C’est-à-dire que les valeurs négatives seront comme si elles avaient été incrémentées de (1 + valeur représentable maximum dans le type entier non signé), ce qui les rend positives.

  • Si l’argument est promu en un type entier non signé, mais que la fonction obtient la valeur à l’aide du type entier signé, et que la valeur est représentable dans les deux cas, la fonction obtient la valeur correcte. Si la valeur n’est pas représentable dans les deux cas, le comportement est défini par l’implémentation.

    En pratique, presque toutes les architectures font l’opposé du précédent, c’est-à-dire que la valeur entière signée obtenue correspond à la valeur non signée soustraite de (1 + la plus grande valeur représentable du type entier non signé). J’ai entendu dire que certains étranges peuvent signaler un dépassement d’entier ou quelque chose d’aussi étrange, mais je n’ai jamais eu mes gants sur de telles machines.

La page de manuel man 3 printf (avec la permission du projet de pages de manuel Linux) est assez informative, si vous comparez les règles ci-dessus aux spécificateurs printf. La fonction exemple make_message() à la fin (C99, C11 ou POSIX requirejse pour vsnprintf() ) devrait également être intéressante.

Quand vous écrivez 1 , c’est une constante int . Il n’y a pas d’autre type que le compilateur est autorisé à utiliser. S’il existe un prototype non variadic pour la fonction qui demande un type différent, le compilateur convertira l’entier 1 en type approprié, mais 1 est une constante int . Donc, dans votre exemple, tous les 6 arguments sont int .

Vous devez connaître les types d’arguments avant que la fonction variadique appelée les traite. Avec la famille de fonctions printf() , la chaîne de format lui dit à quoi s’attendre; de même avec la famille de fonctions scanf() .

Notez que les conversions par défaut s’appliquent aux arguments correspondant aux points de suspension d’une fonction variadique. Par exemple, étant donné:

 char c = '\007'; short s = 0xB0hD; float f = 3.1415927; 

un appel à:

 int variadic_function(const char *, ...); 

en utilisant:

 int rc = variadic_function("csf", c, s, f); 

convertit réellement c et s en int et f en double .