35 votes

Détection des expressions constantes de nombres entiers dans les macros

Il y a eu une discussion dans la liste de diffusion du noyau Linux concernant une macro qui teste si son argument est une expression constante entière et est elle-même une expression constante entière.

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

#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 autrement. Cependant, il s'appuie sur sizeof(void) pour être autorisé (et différent de sizeof(int) ), qui est un Extension GNU C .

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


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

0 votes

Vous n'avez pas montré l'utilisation de la macro. Veuillez afficher la Exemple minimal, complet et vérifiable qui montre le problème.

22 votes

@WeatherVane Ce n'est pas une question de débogage ("pourquoi ce code ne fonctionne-t-il pas ?").

0 votes

@melpomene pourquoi cette exemption ? J'aimerais voir comment cela est utilisé.

24voto

user2357112 Points 37737

Utilisez la même idée, où le type d'une ?: dépend du fait qu'un argument soit une constante de type pointeur nul ou une constante ordinaire. void * mais détecte le type avec _Generic :

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

Démonstration sur Ideone. _Generic est une addition C11, donc si vous êtes bloqué sur C99 ou quelque chose de plus ancien, vous ne pourrez pas l'utiliser.

Il faut également prévoir des liens standard pour la définition d'une constante de type pointeur nul y la manière dont les constantes de pointeur nul interagissent avec le type d'une ?: expression :

Une expression constante entière ayant la valeur 0, ou une expression de ce type convertie en type void *, est appelée une constante à pointeur nul.

y

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

0 votes

En effet, _Generic résout ce problème (de la même manière que l'utilisation d'un intégré comme __builtin_types_compatible_p ). Le noyau n'utilise pas C11, mais je n'ai pas marqué la question comme C89 afin d'obtenir autant de solutions que possible dans différentes versions de la norme. Voyons si quelqu'un propose une approche différente !

1 votes

Il est sans doute utile d'ajouter des liens pour expliquer le problème avec sizeof(void) : §6.5.3.4¶1 et §6.2.5¶19. Bien que tout ceci devrait probablement aller dans la question et non dans votre réponse

2 votes

@Acorn : Je n'ai rien trouvé qui fonctionne en C89. Tout ce à quoi je pense nécessite des extensions du C++ ou du compilateur - des choses comme decltype, typeof, SFINAE, surcharge de fonctions, spécialisation des templates, etc. De plus, c'est assez peu pratique que l'option ?: Le truc semble seulement vous permettre de choisir entre un void * ou un autre type de pointeur ; si nous pouvions lui faire choisir entre deux types de pointeur de fonction, nous pourrions tester la fonction sizeof la valeur de retour - ou quelque chose de plus simple que j'ai négligé, si nous pouvions choisir entre deux types de pointeur d'objet, nous pourrions simplement déréférencer et sizeof .

17voto

nemequ Points 10007

Je n'ai pas de solution pour sizeof(void) n'étant pas standard, mais vous pourriez contourner la possibilité que sizeof(void) == sizeof(int) en faisant quelque chose comme :

#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

Editar : J'ai fait un peu de recherche sur les solutions qui fonctionnent sur les différents compilateurs. J'ai encodé toutes les informations suivantes dans le fichier Hedley ; voir le HEDLEY_IS_CONSTANT , HEDLEY_REQUIRE_CONTEXPR y HEDLEY__IS_CONSTEXPR macros. Il est du domaine public et ne comporte qu'un seul en-tête, ce qui permet de l'intégrer facilement à votre projet, ou de copier les parties qui vous intéressent.

C11 Macro & Variantes

macro C11 de l'utilisateur2357112 devrait travailler sur cualquier compilateur C11, mais SunCC y IGP sont actuellement cassés, vous devrez donc les mettre sur liste noire. De plus, IAR définit __STDC_VERSION__ en mode C++, et cette astuce ne fonctionne pas en C++ (AFAIK rien ne va ), donc vous voudrez probablement vous assurer que __cplusplus n'est pas défini. J'ai vérifié qu'il fonctionne réellement 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 une extension :

  • GCC 4.9+.
  • clang ; vérifier avec __has_feature(c_generic_selections) (vous pouvez désactiver l'option -Wc11-extensions avertissement, cependant)
  • ICC 16.0+.
  • XL C/C++ 12.1+

Notez également que, parfois, les compilateurs émettent un avertissement lorsque vous intégrez un fichier int à un void* vous pouvez contourner ce problème en effectuant d'abord un casting vers un intptr_t puis a 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 à la place de intptr_t et vous n'avez pas besoin d'inclure stdint.h .

Une autre mise en œuvre possible ici est d'utiliser __builtin_types_compatible_p au lieu de _Generic . Je ne connais pas de compilateurs où cela fonctionnerait alors que la macro originale ne le ferait pas, mais cela vous permet de vous sortir d'une situation de -Wpointer-arith avertissement :

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

Cette version devrait fonctionner avec GCC jusqu'à la version 3.1, ainsi qu'avec les compilateurs qui définissent l'option __GNUC__ / __GNUC_MINOR__ à des valeurs qui indiquent 3.1 comme clang et ICC.

Macro de cette réponse

Tout compilateur qui supporte sizeof(void) devrait fonctionner, mais il y a de fortes chances que vous rencontriez un avertissement (tel que -Wpointer-arith ). Ceci étant dit, les compilateurs AFAICT qui hacer soutien sizeof(void) semblent l'avoir toujours fait, donc cualquier de ces compilateurs devrait fonctionner :

  • CCG
  • Clang (et les compilateurs construits en dans, qui définissent aussi __clang__ )
  • ICC (testé 18.0)
  • XL C/C++ (testé en 13.1.6)
  • TI (testé 8.0)
  • TinyCC

__builtin_constant_p

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

  • GCC 3.1+.
  • Clang
  • ICC (testé 18.0)
  • TinyCC 0.9.19+
  • armcc 5.04+
  • XL C/C++ (non documenté, mais il fonctionne certainement en 13.1.6+)

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

Par contre, si vous voulez vérifier que la valeur est bien un ICE selon la norme, n'utilisez pas __builtin_constant_p . À 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 l'utiliser lorsque vous déclarez un tableau dans une macro si vous voulez que le compilateur affiche une erreur si vous utilisez un VLA :

char foo[REQUIRE_ICE(bar)];

Cela dit, GCC et clang implémentent tous les deux une fonction -Wvla que vous pouvez utiliser à la place. L'avantage de -Wvla est qu'elle ne nécessite 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 paramètres de tableau conformes déclenchera également le diagnostic, donc si vous voulez éviter un grand nombre de faux positifs, cette macro peut être votre meilleure option.

Compilateurs qui ne supportent rien

  • MSVC
  • DMC

Les idées sont les bienvenues :)

0 votes

Je serais intéressé de savoir pourquoi le vote négatif... AFAICT c'est une amélioration raisonnable de l'original, ce qui est utile car il ne nécessite pas C11 (MSVC quelqu'un ?), et j'ai noté que c'est seulement une réponse partielle.

0 votes

:) parce que c'est partiel :)

3 votes

Je suppose que SO n'est pas vraiment conçu pour la collaboration. Néanmoins, comme il est légèrement utile, je préfère le laisser en place, même si cela coûte un peu de réputation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X