Pourquoi #define TRUE (1 == 1) dans une macro booléenne C au lieu de simplement 1?

J’ai vu des définitions en C

#define TRUE (1==1) #define FALSE (!TRUE) 

Est-ce nécessaire? Quel est l’avantage de simplement définir VRAI comme 1 et FAUX comme 0?

Cette approche utilisera le type boolean réel (et le transformera en true et false ) si le compilateur le prend en charge. (spécifiquement, C ++)

Cependant, il serait préférable de vérifier si C ++ est utilisé (via la macro __cplusplus ) et d’utiliser réellement true et false .

Dans un compilateur C, cela équivaut à 0 et 1 .
(notez que la suppression des parenthèses briserait cela en raison de l’ordre des opérations)

La réponse est la portabilité. Les valeurs numériques de TRUE et FALSE ne sont pas importantes. L’important est qu’une instruction comme if (1 < 2) évaluée à if (TRUE) et qu'une instruction comme if (1 > 2) évaluée à if (FALSE) .

Certes, en C, (1 < 2) évalue à 1 et (1 > 2) à 0 , comme d'autres l'ont dit, il n'y a pas de différence pratique en ce qui concerne le compilateur. Mais en laissant le compilateur définir TRUE et FALSE selon ses propres règles, vous expliquez leur signification aux programmeurs et vous garantissez la cohérence de votre programme et de toute autre bibliothèque (en supposant que l’autre bibliothèque suit les normes C… vous être stupéfait).


Un peu d'histoire
Certains BASIC ont défini FALSE sur 0 et TRUE sur -1 . Comme beaucoup de langages modernes, ils ont interprété toute valeur non nulle comme TRUE , mais ils ont évalué des expressions booléennes qui étaient vraies comme -1 . Leur opération NOT été implémentée en ajoutant 1 et en retournant le signe, car il était efficace de le faire de cette façon. Donc 'NOT x' est devenu -(x+1) . Un effet secondaire de ceci est qu'une valeur comme 5 évaluée à TRUE , mais NOT 5 évaluée à -6 , ce qui est également TRUE ! Trouver ce genre de bogue n'est pas amusant.

Les meilleures pratiques
Étant donné les règles de facto que zéro est interprété comme FALSE et que toute valeur non nulle est interprétée comme TRUE , vous ne devriez jamais comparer des expressions à l'apparence booléenne à TRUE ou FALSE . Exemples:

 if (thisValue == FALSE) // Don't do this! if (thatValue == TRUE) // Or this! if (otherValue != TRUE) // Whatever you do, don't do this! 

Pourquoi? Parce que de nombreux programmeurs utilisent le raccourci de traiter int s comme bool s. Ils ne sont pas les mêmes, mais les compilateurs le permettent généralement. Ainsi, par exemple, il est parfaitement légal d'écrire

 if (strcmp(yourSsortingng, mySsortingng) == TRUE) // Wrong!!! 

Cela semble légitime, et le compilateur l'acceptera volontiers, mais cela ne fait probablement pas ce que vous voulez. C'est parce que la valeur de retour de strcmp() est

0 si yourSsortingng == mySsortingng
<0 si yourSsortingng < myString
> 0 si yourSsortingng > mySsortingng

Donc, la ligne ci-dessus ne retourne TRUE que lorsque votre yourSsortingng > mySsortingng .

La bonne façon de faire est soit

 // Valid, but still treats int as bool. if (strcmp(yourSsortingng, mySsortingng)) 

ou

 // Better: lingustically clear, comstackr will optimize. if (strcmp(yourSsortingng, mySsortingng) != 0) 

De même:

 if (someBoolValue == FALSE) // Redundant. if (!someBoolValue) // Better. return (x > 0) ? TRUE : FALSE; // You're fired. return (x > 0); // Simpler, clearer, correct. if (ptr == NULL) // Perfect: compares pointers. if (!ptr) // Sleazy, but short and valid. if (ptr == FALSE) // Whatisthisidonteven. 

Vous trouverez souvent certains de ces "mauvais exemples" dans le code de production, et de nombreux programmeurs expérimentés ne jurent que par eux: ils fonctionnent, certains sont plus courts que leurs alternatives correctes (pédantes?) Et les idiomes sont presque universellement reconnus. Mais considérez ceci: les "bonnes" versions ne sont pas moins efficaces, elles sont garanties pour être portables, elles réussiront même les linters les plus ssortingcts, et même les nouveaux programmeurs les comprendront.

N'est-ce pas la peine?

L’astuce (1 == 1) est utile pour définir TRUE de manière transparente pour C, tout en offrant un meilleur typage en C ++. Le même code peut être interprété comme C ou C ++ si vous écrivez dans un dialecte appelé “Clean C” (qui comstack en C ou C ++) ou si vous écrivez des fichiers d’en-tête API pouvant être utilisés par des programmeurs C ou C ++.

Dans les unités de traduction C, 1 == 1 a exactement la même signification que 1 ; et 1 == 0 a la même signification que 0 . Cependant, dans les unités de traduction C ++, 1 == 1 a le type bool . Ainsi, la macro TRUE définie de cette façon s’intègre mieux en C ++.

Un exemple de la façon dont il s’intègre mieux est que, par exemple, si la fonction foo a des surcharges pour int et pour bool , alors foo(TRUE) choisira la surcharge bool . Si TRUE est défini sur 1 , alors il ne fonctionnera pas correctement dans C ++. foo(TRUE) voudra la surcharge int .

Bien sûr, C99 a introduit bool , true et false et ceux-ci peuvent être utilisés dans les fichiers d’en-tête qui fonctionnent avec C99 et avec C.

Toutefois:

  • cette pratique de définir TRUE et FALSE comme (0==0) et (1==0) est antérieure à C99.
  • il y a encore de bonnes raisons de restr loin de C99 et de travailler avec C90.

Si vous travaillez dans un projet mixte C et C ++, et que vous ne voulez pas utiliser C99, définissez plutôt la valeur minuscule true , false et bool place.

 #ifndef __cplusplus typedef int bool; #define true (0==0) #define false (!true) #endif 

Cela étant dit, l’astuce 0==0 était (est?) Utilisée par certains programmeurs, même dans le code qui n’a jamais été conçu pour interagir avec C ++. Cela n’achète rien et suggère que le programmeur a une mauvaise compréhension du fonctionnement des booléens en C.


Dans le cas où l’explication C ++ n’était pas claire, voici un programme de test:

 #include  void foo(bool x) { std::puts("bool"); } void foo(int x) { std::puts("int"); } int main() { foo(1 == 1); foo(1); return 0; } 

Le résultat:

 bool int 

En ce qui concerne la question des commentaires sur la façon dont sont les fonctions C ++ surchargées concernant la programmation mixte C et C ++. Ceux-ci illustrent simplement une différence de type. Une raison valable pour vouloir qu’une true constante soit bool lorsqu’elle est compilée en C ++ est pour des diagnostics propres. À ses niveaux d’avertissement les plus élevés, un compilateur C ++ pourrait nous avertir d’une conversion si nous transmettons un entier comme paramètre bool . Une des raisons pour écrire dans Clean C est non seulement que notre code est plus portable (car il est compris par les compilateurs C ++, pas seulement les compilateurs C), mais nous pouvons également bénéficier des avis de diagnostic des compilateurs C ++.

 #define TRUE (1==1) #define FALSE (!TRUE) 

est équivalent à

 #define TRUE 1 #define FALSE 0 

en C.

Le résultat des opérateurs relationnels est 0 ou 1 . 1==1 est garanti pour être évalué à 1 et !(1==1) est garanti pour être évalué à 0 .

Il n’y a absolument aucune raison d’utiliser le premier formulaire. Notez que la première forme n’est cependant pas moins efficace car sur presque tous les compilateurs, une expression constante est évaluée au moment de la compilation plutôt qu’au moment de l’exécution. Ceci est autorisé selon cette règle:

(C99, 6.6p2) “Une expression constante peut être évaluée lors de la traduction plutôt que de l’exécution, et peut donc être utilisée dans n’importe quel endroit où une constante peut être.”

PC-Lint émettra même un message (506, valeur constante booléenne) si vous n’utilisez pas de littéral pour les macros TRUE et FALSE :

Pour C, TRUE devrait être défini à 1 . Cependant, d’autres langages utilisent des quantités autres que 1, de sorte que certains programmeurs estiment que !0 joue en toute sécurité.

En C99, les définitions stdbool.h pour les macros booléennes true et false utilisent directement les littéraux:

 #define true 1 #define false 0 

Outre C ++ (déjà mentionné), un autre avantage concerne les outils d’parsing statique. Le compilateur éliminera tout manque d’efficacité, mais un parsingur statique peut utiliser ses propres types abstraits pour distinguer les résultats de comparaison des autres types d’entiers. Il sait donc implicitement que TRUE doit être le résultat d’une comparaison et ne doit pas être considéré comme compatible. avec un entier

Il est évident que C dit qu’ils sont compatibles, mais vous pouvez choisir d’interdire l’utilisation délibérée de cette fonctionnalité pour mettre en évidence des bogues – par exemple, quelqu’un pourrait avoir des problèmes de confusion et && ou avoir annulé la priorité de ses opérateurs.

La différence pratique est nulle. 0 est évalué à false et 1 à true . Le fait d’utiliser une expression booléenne ( 1 == 1 ) ou 1 , pour définir true , ne fait aucune différence. Ils sont tous deux évalués à int .

Notez que la bibliothèque standard C fournit un en-tête spécifique pour définir des booléens: stdbool.h .

Nous ne connaissons pas la valeur exacte à laquelle TRUE est égal et les compilateurs peuvent avoir leurs propres définitions. Donc, ce que vous privode est d’utiliser l’interne du compilateur pour la définition. Ce n’est pas toujours nécessaire si vous avez de bonnes habitudes de programmation, mais vous pouvez éviter les problèmes liés à un mauvais style de codage, par exemple:

si ((a> b) == TRUE)

Cela peut être un désastre si vous définissez manuellement VRAI à 1, alors que la valeur interne de VRAI en est une autre.

  1. Élément de la liste

En général, dans le langage de programmation C, 1 est défini sur true et 0 sur false. Par conséquent, vous voyez souvent ce qui suit:

 #define TRUE 1 #define FALSE 0 

Cependant, tout nombre différent de 0 serait également évalué à true dans une instruction conditionnelle. Par conséquent, en utilisant ce qui suit:

 #define TRUE (1==1) #define FALSE (!TRUE) 

Vous pouvez simplement montrer explicitement que vous essayez de jouer en toute sécurité en rendant faux égal à ce qui n’est pas vrai.