web-dev-qa-db-fra.com

Détection des expressions constantes entières dans les macros

Dans la liste de diffusion du noyau Linux, il a été question d’une macro qui teste si son argument est une expression constante entière et une expression constante entière elle-même.

Une approche particulièrement intelligente qui n'utilise pas les éléments intégrés, proposée par Martin Uecker (en prenant inspiration de glibc's tgmath.h ), est la suivante:

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

Cette macro se développe en une expression constante entière de valeur 1 Si l'argument est une expression constante entière, 0 Sinon. Cependant, il faut que sizeof(void) soit autorisé (et différent de sizeof(int)), qui est un extension GNU C .

Est-il possible d'écrire une telle macro sans fonctions intégrées et sans s'appuyer sur des extensions de langage? Si oui, évalue-t-il son argument?


Pour une explication de la macro présentée ci-dessus, voir à la place: macro __is_constexpr du noyau Linux

35
Acorn

Utilisez la même idée, où le type d'un ?: L’expression dépend du fait qu’un argument est une constante de pointeur nulle ou un void *, mais détecte le type avec _Generic :

#define ICE_P(x) _Generic((1? (void *) ((x)*0) : (int *) 0), int*: 1, void*: 0)

Démo sur Ideone._Generic est un ajout de C11, donc si vous êtes bloqué sur C99 ou quelque chose de plus tôt, vous ne pourrez pas l’utiliser.

Vous devez également disposer de liens standard pour la définition d'une constante de pointeur null et la manière dont les constantes de pointeur null interagissent avec le type d'un ?: expression :

Une expression constante entière avec la valeur 0, ou une telle expression convertie dans le type void *, est appelée une constante de pointeur nulle.

et

Si les deuxième et troisième opérandes sont tous deux des pointeurs ou que l'un est une constante de pointeur nulle et que l'autre est un pointeur, le type de résultat est un pointeur sur un type qualifié avec tous les qualificateurs de type des types référencés par les deux opérandes. De plus, si les deux opérandes sont des pointeurs sur des types compatibles ou sur des versions de types compatibles qualifiées différemment, le type de résultat est un pointeur sur une version qualifiée appropriée du type composite; si un opérande est une constante de pointeur nulle, le résultat a le type de l'autre opérande; sinon, un opérande est un pointeur sur void ou une version qualifiée de void, auquel cas le type de résultat est un pointeur sur une version qualifiée appropriée de void .

25
user2357112

Je n'ai pas de solution pour que sizeof(void) ne soit pas standard, mais vous pouvez contourner la possibilité que sizeof(void) == sizeof(int) soit comme suit:

#define ICE_P(x) ( \
  sizeof(void) != \
  sizeof(*( \
    1 ? \
      ((void*) ((x) * 0L) ) : \
      ((struct { char v[sizeof(void) * 2]; } *) 1) \
    ) \
  ) \
)

Je sais que ce n'est pas une réponse complète, mais c'est légèrement plus près…

Edit : J'ai fait quelques recherches sur les solutions qui fonctionnent sur différents compilateurs. J'ai encodé toutes les informations suivantes dans Hedley ; voir les macros HEDLEY_IS_CONSTANT, HEDLEY_REQUIRE_CONTEXPR et HEDLEY__IS_CONSTEXPR. C'est un domaine public, et un en-tête unique, il est donc très facile de simplement insérer votre projet dans votre projet ou de copier les éléments qui vous intéressent.

C11 Macro & Variantes

la macro C11 de user2357112 devrait fonctionne sur tout compilateur C11 , mais SunCC et [~ # ~] pgi [~ # ~] sont actuellement en panne, vous devrez donc les mettre sur liste noire. En outre, IAR définit __STDC_VERSION__ En mode C++, et cette astuce ne fonctionne pas en C++ (autant que je sache rien ne le fait ), vous voudrez probablement vous assurer que __cplusplus N'est pas défini. J'ai vérifié que cela fonctionne vraiment sur GCC, Clang (et les compilateurs dérivés de Clang comme emscripten), ICC, IAR et XL C/C++.

En dehors de cela, certains compilateurs supportent _Generic Même dans les anciens modes comme extension:

  • GCC 4.9 +
  • bruit; vérifier avec __has_feature(c_generic_selections) (vous voudrez peut-être désactiver l'avertissement -Wc11-extensions, cependant)
  • ICC 16.0 +
  • XL C/C++ 12.1+

Notez également que les compilateurs émettent parfois un avertissement lorsque vous convertissez un int en un void*; vous pouvez contourner ceci en commençant par lancer un intptr_t alors à void*:

#define ICE_P(expr) _Generic((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)

Ou, pour les compilateurs (tels que GCC) qui définissent __INTPTR_TYPE__, Vous pouvez l'utiliser au lieu de intptr_t Et vous n'avez pas besoin d'inclure stdint.h.

Une autre implémentation possible ici consiste à utiliser __builtin_types_compatible_p Au lieu de _Generic. Je ne suis au courant d'aucun compilateur où cela fonctionnerait, mais pas la macro d'origine, mais cela vous évite d'avoir un avertissement -Wpointer-arith:

#define IS_CONSTEXPR(expr) \
  __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)

Cette version devrait fonctionner avec GCC depuis la version 3.1, ainsi qu'avec les compilateurs qui définissent __GNUC__/__GNUC_MINOR__ À des valeurs indiquant ≥ 3.1 telles que clang et ICC.

Macro de cette réponse

Tout compilateur prenant en charge sizeof(void) devrait fonctionner, mais il y a de bonnes chances que vous rencontriez un avertissement (tel que -Wpointer-arith). Cela dit, les compilateurs AFAICT qui do supportent sizeof(void) semblent l'avoir toujours fait, donc toute la version de ces compilateurs devrait fonctionner:

  • GCC
  • Clang (et compilateurs intégrés, qui définissent également __clang__)
  • ICC (testé 18.0)
  • XL C/C++ (testé en 13.1.6)
  • TI (testé 8.0)
  • TinyCC

__builtin_constant_p

Selon votre cas d'utilisation, il peut être préférable d'utiliser __builtin_constant_p Sur les compilateurs qui le prennent en charge. C'est un peu plus général (et plus nébuleux) qu'une expression constante entière; il indique simplement que le compilateur connaît la valeur au moment de la compilation. Ces compilateurs sont connus pour le supporter:

  • GCC 3.1+
  • Bruit
  • ICC (testé 18.0)
  • TinyCC 0.9.19+
  • armcc 5.04+
  • XL C/C++ (non documenté, mais cela fonctionne définitivement dans 13.1.6+)

Si vous utilisez la macro pour choisir entre un chemin de code que le compilateur peut plier de manière constante s'il connaît la valeur au moment de la compilation mais est lent au moment de l'exécution et un chemin de code qui est une boîte noire pour le compilateur mais qui est rapide au moment de l'exécution, utilisez __builtin_constant_p.

OTOH, si vous voulez vérifier que la valeur est bien une ICE selon la norme, n'utilisez pas __builtin_constant_p. A titre d'exemple, voici une macro qui retournera expr si le expr est un ICE, mais -1 s'il ne l'est pas:

#if defined(ICE_P)
#  define REQUIRE_ICE(expr) (ICE_P(expr) ? (expr) : (-1))
#else
#  define REQUIRE_ICE(expr) (expr)
#endif

Vous pouvez ensuite utiliser cela lorsque vous déclarez un tableau dans une macro si le compilateur doit afficher une erreur si vous utilisez un VLA:

char foo[REQUIRE_ICE(bar)];

Cela dit, GCC et clang implémentent tous deux un avertissement -Wvla Que vous voudrez peut-être utiliser à la place. L’avantage de -Wvla Est qu’il n’exige pas de modification du code source (c’est-à-dire que vous pouvez simplement écrire char foo[bar];). Les inconvénients sont qu’il n’est pas aussi largement supporté, et que l’utilisation de des paramètres de tableau conformes déclenchera également le diagnostic. Par conséquent, si vous souhaitez éviter beaucoup de faux positifs, cette macro peut être votre meilleur pari.

Des compilateurs qui ne supportent rien

  • MSVC
  • DMC

Idées bienvenues :)

18
nemequ