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

160

J'ai vu des définitions en C

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

Est-ce nécessaire? Quel est l'avantage de définir simplement TRUE comme 1 et FALSE comme 0?

Robert Harvey
la source
35
Et plus #define TRUE (’/’/’/’):; #define FALSE (’-’-’-’)(extrait de coding-guidelines.com/cbook/cbook1_1.pdf page 871)
osgx
2
Non, c'est de la paranoïa chez les ignorants ^ Wunderinformé, vraiment. En C, 1 et 0 font en toutes circonstances la même chose.
Jens
@osgx Qu'est-ce que cela signifie?
mrgloom

Réponses:

155

Cette approche utilisera le booleantype réel (et sera résolu en trueet 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 __cplusplusmacro) et d'utiliser réellement trueet false.

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

SLaks
la source
7
C'est incorrect, les booléens ne sont pas utilisés ici. Le résultat de 1==1est un int. (voir stackoverflow.com/questions/7687403/… .)
Mat
4
@Mat: Même en C ++, avec le booleantype?
SLaks
9
La question est étiquetée C, mais en effet en C ++ les opérateurs relationnels renvoient trueou false.
Mat
5
@Mat: Je suppose qu'un tel code est écrit dans les en-têtes C afin de bien jouer avec C ++
SLaks
20
@SLaks S'il voulait bien jouer avec C ++, il le ferait #define TRUE trueet à #define FALSE falsechaque fois qu'il __cplusplusest défini.
Nikos C.
137

La réponse est la portabilité. Les valeurs numériques de TRUEet FALSEne sont pas importantes. Ce qui est important, c'est qu'une instruction comme if (1 < 2)évalue à if (TRUE)et une instruction comme if (1 > 2)évalue à if (FALSE).

Certes, en C, (1 < 2)évalue 1et (1 > 2)évalue à 0, donc 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 TRUEet FALSEselon ses propres règles, vous rendez leur signification explicite aux programmeurs, et vous garantissez la cohérence au sein de votre programme et de toute autre bibliothèque (en supposant que l'autre bibliothèque respecte les normes C ... être surpris).


Un peu d'histoire
Certains BASIC définis FALSEcomme 0et TRUEcomme -1. Comme de nombreux langages modernes, ils ont interprété toute valeur non nulle comme TRUE, mais ils ont évalué les expressions booléennes qui étaient vraies comme -1. Leur NOTopération a été mise en œuvre 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évalue à TRUE, mais NOT 5évalue à -6, qui est aussi TRUE! Trouver ce genre de bogue n'est pas amusant.

Meilleures pratiques
Étant donné les règles de facto selon lesquelles zéro est interprété FALSEet toute valeur non nulle est interprétée comme TRUE, vous ne devez jamais comparer des expressions booléennes à TRUEouFALSE . 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 pour traiter ints comme bools. Ce 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(yourString, myString) == TRUE)  // Wrong!!!

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

      0 si yourString == myString
    <0 si yourString < myString
    > 0 siyourString > myString

Ainsi, la ligne ci-dessus TRUEne renvoie que quand yourString > myString.

La bonne façon de procéder est soit

// Valid, but still treats int as bool.
if (strcmp(yourString, myString))

ou

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 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 (pédantiquement?) Correctes, et les idiomes sont presque universellement reconnus. Mais considérez: les «bonnes» versions ne sont pas moins efficaces, elles sont garanties pour être portables, elles passeront même les linters les plus stricts, et même les nouveaux programmeurs les comprendront.

Cela ne vaut-il pas la peine?

Adam Liss
la source
6
(1==1)n'est pas plus portable que 1. Les règles propres au compilateur sont celles du langage C, qui est clair et sans ambiguïté sur la sémantique de l'égalité et des opérateurs relationnels. Je n'ai jamais vu un compilateur se tromper.
Keith Thompson
1
En fait, la valeur renvoyée par strcmpest connue pour être inférieure, égale ou supérieure à 0. Elle n'est pas garantie d'être -1, 0 ou 1 et il y a des plates-formes dans la nature qui ne renvoient pas ces valeurs pour gagner en vitesse de mise en œuvre. Donc, si strcmp(a, b) == TRUEalors, a > bmais l'implication inverse pourrait ne pas tenir.
Maciej Piechotka
2
@KeithThompson - Peut-être que «portabilité» n'était pas le bon terme. Mais le fait demeure que (1 == 1) est une valeur booléenne; 1 ne l'est pas.
Adam Liss
2
@AdamLiss: En C, (1==1)et 1sont toutes deux des expressions constantes de type intavec la valeur 1. Elles sont sémantiquement identiques. Je suppose que vous pouvez écrire du code qui s'adresse aux lecteurs qui ne le savent pas, mais où cela se termine-t-il?
Keith Thompson
2
«not» 5 est, en effet, -6, au niveau du bit.
woliveirajr
51

L' (1 == 1)astuce est utile pour définir TRUEd'une 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 se compile en C ou C ++) ou si vous écrivez des fichiers d'en-tête API qui peuvent être utilisés par les programmeurs C ou C ++.

Dans les unités de traduction C, 1 == 1a exactement la même signification que 1; et 1 == 0a la même signification que 0. Cependant, dans les unités de traduction C ++, 1 == 1a type bool. Ainsi, la TRUEmacro ainsi définie s'intègre mieux dans C ++.

Un exemple de la façon dont il s'intègre mieux est que, par exemple, si la fonction fooa des surcharges pour intet pour bool, alors foo(TRUE)choisira la boolsurcharge. Si TRUEest simplement défini comme 1, alors cela ne fonctionnera pas bien dans le C ++. foo(TRUE)voudra la intsurcharge.

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

Toutefois:

  • cette pratique de définition TRUEet au FALSEfur (0==0)et (1==0)à mesure de C99.
  • il y a encore de bonnes raisons de ne pas utiliser C99 et de travailler avec C90.

Si vous travaillez dans un mélange C et le projet de C, et ne veulent pas C99, définir le minuscule true, falseet au boollieu.

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

Cela étant dit, l' 0==0astuce a été (est?) Utilisée par certains programmeurs même dans du code qui n'a jamais été destiné à interagir avec C ++ de quelque manière que ce soit. Cela n'achète rien et suggère que le programmeur a une mauvaise compréhension du fonctionnement des booléens en C.


Au cas où l'explication C ++ ne serait pas claire, voici un programme de test:

#include <cstdio>

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

Quant à la question des commentaires de savoir comment sont surchargées les fonctions C ++ pertinentes pour la programmation mixte C et C ++. Ceux-ci illustrent simplement une différence de type. Une raison valable de vouloir qu'une trueconstante soit boolcompilée en C ++ est pour des diagnostics propres. À ses niveaux d'avertissement les plus élevés, un compilateur C ++ peut nous avertir d'une conversion si nous transmettons un entier en boolparamètre. Une des raisons d'écrire en Clean C n'est pas seulement que notre code est plus portable (puisqu'il est compris par les compilateurs C ++, pas seulement les compilateurs C), mais nous pouvons bénéficier des avis de diagnostic des compilateurs C ++.

Kaz
la source
3
Réponse excellente et sous-estimée. Il n'est pas du tout évident que les deux définitions de TRUEdifféreront sous C ++.
user4815162342
4
Comment les fonctions surchargées sont-elles pertinentes pour le code qui se compile à la fois en C et C ++?
Keith Thompson
@KeithThompson Il ne s'agit pas seulement de surcharge, mais d'une saisie correcte en général, la surcharge n'est que l'exemple le plus pratique quand elle entre en jeu. Bien sûr, le code C ++ sans surcharges, les modèles et tous ces trucs "compliqués" supprimés pour la "compatibilité C" ne se soucient pas du tout des types, mais cela ne signifie pas qu'il faut renverser les limitations de type conceptuel dans un langage donné .
Christian Rau
1
@ChristianRau: Que voulez-vous dire "ne se soucie pas vraiment des types"? Les types sont au cœur du langage C; chaque expression, valeur et objet d'un programme C a un type bien défini. Si vous souhaitez définir quelque chose différemment en C et en C ++ (dans les rares cas où vous avez réellement besoin d'écrire du code qui se compile à la fois en C et en C ++), vous pouvez utiliser #ifdef __cpluspluspour exprimer votre intention beaucoup plus clairement.
Keith Thompson
@KeithThompson Oui, je sais à quel point les types sont importants. C'est juste que sans tous les éléments sensibles au type comme la surcharge et les modèles, des choses comme la différenciation entre boolet intn'ont pas beaucoup d'importance en pratique, car ils sont implicitement convertibles les uns aux autres (et en C en fait "les mêmes" , notez les guillemets , cependant) et il n'y a pas beaucoup de situations dans lesquelles vous devez vraiment désambuiguer entre les deux. «pas beaucoup» était probablement trop lourd, «beaucoup moins comparé au code utilisant des modèles et la surcharge» aurait peut-être été mieux.
Christian Rau
18
#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 0ou 1. 1==1est garanti pour être évalué 1et !(1==1)est garanti pour être évalué 0.

Il n'y a absolument aucune raison d'utiliser le premier formulaire. Notez que le premier formulaire 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 pendant la traduction plutôt que pendant l'exécution, et en conséquence peut être utilisée à n'importe quel endroit où se trouve une constante."

PC-Lint émettra même un message (506, valeur constante booléenne) si vous n'utilisez pas de littéral pour TRUEet de FALSEmacros:

Pour C, TRUEdoit être défini comme étant 1. Cependant, d'autres langages utilisent des quantités autres que 1, de sorte que certains programmeurs estiment que !0c'est en toute sécurité.

Aussi en C99, les stdbool.hdéfinitions des macros booléennes trueet false utilisent directement des littéraux:

#define true   1
#define false  0
ouah
la source
1
J'ai un doute, TRUE est remplacé à chaque utilisation par 1 == 1, alors que le simple fait d'utiliser 1 remplacera 1, la première méthode n'est-elle pas une surcharge de comparaison supplémentaire ... ou elle est rendue compilateur optimisé?
pinkpanther
4
Les expressions constantes @pinkpanther sont généralement évaluées au moment de la compilation et n'induisent donc pas de surcharge.
ouah
2
1==1est garanti d'être évalué à1
ouah
3
@NikosC. c'est un peu une bonne question. Cela compte pour le code de la forme if(foo == true), qui passera d'une simple mauvaise pratique à un buggy pur et simple.
djechlin
1
+1 pour avoir signalé les dangers de (x == TRUE)peut avoir une valeur de vérité différente de x.
Joshua Taylor
12

Outre le C ++ (déjà mentionné), un autre avantage concerne les outils d'analyse statique. Le compilateur supprimera toute inefficacité, mais un analyseur statique peut utiliser ses propres types abstraits pour faire la distinction entre les résultats de comparaison et d'autres types d'entiers, de sorte qu'il sait implicitement que TRUE doit être le résultat d'une comparaison et ne doit pas être supposé compatible avec un entier.

De toute évidence, C dit qu'ils sont compatibles, mais vous pouvez choisir d'interdire l'utilisation délibérée de cette fonctionnalité pour aider à mettre en évidence les bogues - par exemple, lorsque quelqu'un pourrait avoir de la confusion &et / &&ou il a bafoué sa priorité d'opérateur.

sh1
la source
1
C'est un bon point, et peut-être que certains de ces outils peuvent même attraper un code idiot if (boolean_var == TRUE) par le biais d'une expansion if (boolean_var == (1 == 1))auquel, grâce aux informations de type améliorées du (1 == 1)nœud, s'inscrit dans le modèle if (<*> == <boolean_expr>).
Kaz
4

La différence pratique est nulle. 0est évalué à falseet 1est évalué à true. Le fait que vous utilisiez 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 booléens: stdbool.h.

Chaussure
la source
bien sûr que vous ne l'avez pas fait ... mais certaines personnes pourraient penser autrement, surtout pour les nombres négatifs, c'est pourquoi :)
pinkpanther
Quoi? Vous l'avez à l'envers. trueest évalué à 1et falseest évalué à 0. C ne connaît pas les types booléens natifs, ce ne sont que des entiers.
djechlin
En C, les opérateurs relationnels et d'égalité donnent des résultats de type int, avec valeur 0ou 1. C a un type booléen réel ( _Bool, avec une macro booldéfinie dans <stdbool.h>, mais qui n'a été ajoutée qu'en C99, ce qui n'a pas changé la sémantique des opérateurs pour utiliser le nouveau type.
Keith Thompson
@djechlin: Depuis la norme 1999, C n'avoir un type booléen natif. Il s'appelle et a . _Bool<stdbool.h>#define bool _Bool
Keith Thompson
@KeithThompson, vous avez raison d' 1 == 1être évalué comme int. Édité.
Chaussure
3

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 celui interne du compilateur pour la définition. Ce n'est pas toujours nécessaire si vous avez de bonnes habitudes de programmation, mais cela peut éviter des problèmes pour un mauvais style de codage, par exemple:

si ((a> b) == VRAI)

Cela pourrait être un désastre si vous définissez manuellement TRUE comme 1, tandis que la valeur interne de TRUE en est une autre.

capiggue
la source
En C, l' >opérateur donne toujours 1 pour vrai, 0 pour faux. Il n'y a aucune possibilité qu'un compilateur C se trompe. Les comparaisons d'égalité TRUEet de FALSEstyle médiocre; ce qui précède est plus clairement écrit comme if (a > b). Mais l'idée que différents compilateurs C peuvent traiter la vérité et le faux différemment est tout simplement incorrecte.
Keith Thompson
2
  1. Élément de liste

En règle générale, dans le langage de programmation C, 1 est défini comme vrai et 0 est défini comme faux. C'est pourquoi vous voyez assez souvent ce qui suit:

#define TRUE 1 
#define FALSE 0

Cependant, tout nombre non égal à 0 serait également évalué à vrai 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 la sécurité en rendant faux égal à ce qui n'est pas vrai.

Sabashan Ragavan
la source
4
Je n'appellerais pas ça «jouer la sécurité» - plutôt, vous vous donnez un faux sentiment de sécurité.
dodgethesteamroller