为什么在C语言的布尔宏中,使用#define TRUE (1==1)而不是简单地使用1?

163

我曾经在 C 中看过定义

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

这是必要的吗?与仅将TRUE定义为1和FALSE定义为0相比有什么好处?


37
还有:#define TRUE (’/’/’/’); #define FALSE (’-’-’-’)(摘自http://www.coding-guidelines.com/cbook/cbook1_1.pdf的第871页) - osgx
2
不,这只是无知者的偏执狂。在C语言中,1和0在所有情况下都是相同的。 - Jens
@osgx 是什么意思? - mrgloom
8个回答

157

该方法将使用实际的boolean类型(并解析为truefalse),如果编译器支持的话。(具体来说,是C++)

然而,更好的做法是检查是否正在使用C++(通过__cplusplus宏),并实际使用truefalse

在C编译器中,这相当于01。(请注意,删除括号会由于运算次序而导致出错)


7
这是错误的,这里没有使用布尔值。1==1的结果是一个整数(int)。(参见https://dev59.com/Gmsz5IYBdhLWcg3wwKmh 。) - Mat
4
即使在 C++ 中,也可以使用 boolean 类型吗? - SLaks
9
这个问题被标记为C语言,但实际上在C++中,关系运算符返回的是 truefalse - Mat
5
我猜这种代码是写在C头文件中的,以便与C++兼容。 - SLaks
20
如果它想要与C++友好地协作,它应该在__cplusplus被定义时将#define TRUE true#define FALSE false - Nikos C.
显示剩余10条评论

141
答案是可移植性。TRUE和FALSE的数值不重要,重要的是像if (1 < 2)这样的语句被评估为if (TRUE),而像if (1 > 2)这样的语句被评估为if (FALSE)。
诚然,在C中,(1 < 2)被评估为1,(1 > 2)被评估为0,因此像其他人所说的那样,就编译器而言没有实际区别。但是,通过让编译器根据自己的规则定义TRUE和FALSE,您正在向程序员明确它们的含义,并保证程序及任何其他库(假设其他库遵循C标准...您会惊讶)的一致性。
一些BASIC将FALSE定义为0,将TRUE定义为-1。与许多现代语言一样,它们将任何非零值“解释”为TRUE,但它们将真值布尔表达式“评估”为-1。它们的NOT操作通过添加1并翻转符号来实现,因为这种方式是有效的。因此,“NOT x”变成了-(x + 1)。这样做的一个副作用是,像5这样的值被评估为TRUE,但NOT 5被评估为-6,它也是TRUE!找到这种错误并不好玩。
鉴于事实上的规则是零被解释为FALSE,任何非零值都被解释为TRUE,您不应该将看起来像布尔表达式的表达式与TRUE或FALSE进行比较。例如:
if (thisValue == FALSE)  // Don't do this!
if (thatValue == TRUE)   // Or this!
if (otherValue != TRUE)  // Whatever you do, don't do this!

为什么?因为许多程序员使用将 int 当作 bool 的快捷方式。虽然它们并不相同,但编译器通常允许这样做。因此,例如,编写以下代码是完全合法的:

if (strcmp(yourString, myString) == TRUE)  // Wrong!!!

这看起来是合法的,编译器会欣然接受,但它可能不做你想要的事情。这是因为 strcmp() 的返回值是:

      当yourString == myString时,返回0
    当yourString < myString时,返回<0
    当yourString > myString时,返回>0

所以上面的语句仅在 yourString > myString 时返回 TRUE

正确的方法是:

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

或者

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 0)

同样地:
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.

你经常会在生产代码中找到一些这样的“不好的例子”,许多有经验的程序员都信誓旦旦地支持它们:它们有效,有些比(教条主义?)正确的替代方案更短,而且这些惯用语几乎被广泛认可。但是请考虑:正确的版本并不会更低效,它们保证可移植,在最严格的语法检查器中也能通过,即使是新手程序员也能理解它们。这难道不值得吗?

6
(1==1)并不比1更具可移植性。编译器遵循C语言的规则,关于相等和关系运算符的语义是明确无误的。我从未见过编译器在这些方面出现错误。 - Keith Thompson
1
实际上,strcmp 返回的值被认为小于、等于或大于 0。它不能保证是 -1、0 或 1,并且有些平台不返回这些值以获得更快的实现速度。因此,如果 strcmp(a, b) == TRUE,则 a > b,但反向蕴含可能不成立。 - Maciej Piechotka
2
@KeithThompson - 或许“可移植性”这个词不太合适。但事实仍然存在,(1==1)是一个布尔值;而1则不是。 - Adam Liss
2
@AdamLiss:在C语言中,(1==1)1都是类型为int且值为1的常量表达式。它们在语义上是相同的。我想你可以编写一些针对不知道这一点的读者的代码,但这会有何止境呢? - Keith Thompson
2
在位元層級上,“not” 5 的確是 -6。 - woliveirajr
显示剩余2条评论

52
(1 == 1)技巧非常有用,可以定义一种对C透明的TRUE方式,同时在C++中提供更好的类型。如果您正在编写称为“Clean C”的方言(它可以编译为C或C ++),或者编写可供C或C ++程序员使用的API头文件,则可以将相同的代码解释为C或C ++。
在C翻译单元中,1 == 11完全相同; 1 == 00相同。但是,在C ++翻译单元中,1 == 1具有类型bool。因此,以这种方式定义的TRUE宏更好地集成到C ++中。
它更好地集成的一个例子是,例如,如果函数foointbool进行了重载,那么foo(TRUE)将选择bool重载。如果TRUE仅被定义为1,则在C ++中不起作用。 foo(TRUE)将需要int重载。
当然,C99引入了booltruefalse,并且这些可以在与C99和C一起使用的头文件中使用。
但是:
  • TRUEFALSE定义为(0 == 0)(1 == 0)的做法早于C99。
  • 仍有很好的理由远离C99并使用C90。
如果您正在进行混合C和C ++项目,并且不想使用C99,则应定义小写的truefalsebool
#ifndef __cplusplus
typedef int bool;
#define true (0==0)
#define false (!true)
#endif

话虽如此,一些程序员甚至在从未打算与C++交互的代码中也使用了0==0技巧。这并没有什么用处,并表明程序员对C语言布尔类型的工作原理存在误解。


如果C++的解释不清楚,这里有一个测试程序:

#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;
}

输出结果:
bool
int

关于评论中提到的C++函数重载与混合C和C++编程有何关联的问题。这只是说明了一种类型差异。在编译为C++时,希望将true常量设置为bool的有效原因是为了清晰的诊断信息。在最高警告级别下,如果我们将整数作为bool参数传递,C++编译器可能会警告我们进行转换。写干净的C代码的一个原因不仅在于我们的代码更具可移植性(因为它被C++编译器理解,而不仅仅是C编译器),还在于我们可以从C++编译器的诊断意见中受益。

3
非常优秀而又被低估的回答。在C++中,TRUE的两个定义不同,这一点并不明显。 - user4815162342
4
重载函数与编译为C和C++的代码有何关联? - Keith Thompson
@KeithThompson,这不仅仅是关于重载,而是关于一般的适当类型,重载只是最实用的例子。当它发挥作用时,当然,没有重载、模板和所有那些为了“C兼容性”而删除的“复杂”东西的C++代码实际上并不太关心类型,但这并不意味着应该推翻给定语言中的概念类型限制。 - Christian Rau
1
@ChristianRau:你所说的“并不是很在意类型”是什么意思?类型是C语言的核心;C程序中的每个表达式、值和对象都有明确定义的类型。如果您想在C和C++中以不同的方式定义某些内容(在罕见情况下,您实际上需要编写能够编译为C和C++的代码),可以使用#ifdef __cplusplus来更清楚地表达您的意图。 - Keith Thompson
@KeithThompson "如果你想在C和C++中定义不同的东西..." - 我也知道这一点,我从未主张(0==0)作为定义"与语言无关的" TRUE 的适当方式,我同意使用#ifdef __cplusplus块会更清晰。但是,尽管不是最好的方法,这个宏仍然是一种方法。唯一激发我发表评论的事情是你对重载函数示例的反应,而实际上它只是一个更本质和概念性问题的例子,即正确类型化的必要性(正如你先前的评论所暗示的那样,你也非常重视这个问题)。 - Christian Rau
显示剩余2条评论

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

等同于

#define TRUE  1
#define FALSE 0

在C中,关系运算符的结果是011==1保证被评估为1,而!(1==1)保证被评估为0。绝对没有理由使用第一种形式。请注意,第一种形式在几乎所有编译器上都不比第二种形式更有效率,因为常量表达式通常在编译时而不是运行时计算。这是根据以下规则允许的:

(C99,6.6p2) "可以在翻译期间评估常量表达式,因此可以在任何可以使用常量的地方使用它。"

即使你不使用TRUEFALSE宏的文字,PC-Lint也会发出消息(506,constant value boolean):

对于C语言,应该将TRUE定义为1。但是,其他语言使用的数量不是1,因此一些程序员认为!0更为保险。

在C99中,stdbool.h定义的布尔宏truefalse直接使用字面值:

#define true   1
#define false  0

1
我有一个疑问,每次使用TRUE时都会被替换为1==1,而仅使用1将替换1,第一种方法是否存在额外的比较开销...或者这是由优化编译器实现的? - pinkpanther
4
常量表达式通常在编译时进行求值,因此不会产生任何额外开销。 - ouah
2
“1==1”保证被计算为“1”。 - ouah
3
@NikosC,这是一个不错的问题。这对于形如if(foo == true)的代码非常重要,否则它将从不良实践变成明显的错误。 - djechlin
1
指出 (x == TRUE) 中的危险性与 x 可能具有不同的真值,这一点非常重要。 - Joshua Taylor
显示剩余4条评论

12

除了已经提到的C++之外,另一个好处是对于静态分析工具。编译器将消除任何低效率,但静态分析器可以使用自己的抽象类型来区分比较结果和其他整数类型,因此它隐含地知道TRUE必须是比较的结果,不应假定与整数兼容。

显然,C语言说它们是兼容的,但您可能选择禁止故意使用该特性以帮助突出错误-例如,某人可能混淆了&&&,或者他们已经弄乱了运算符优先级。


1
这是一个很好的观点,也许其中一些工具甚至可以通过扩展到if (boolean_var == (1 == 1))来捕捉愚蠢的代码if (boolean_var == TRUE),由于(1 == 1)节点的增强类型信息,它会落入模式if (<*> == <boolean_expr>) - Kaz

4
实际上它们没有任何实质性的区别。0会被解释为false1会被解释为true。使用布尔表达式(如1 == 1)或直接使用1来定义true并没有区别。它们都会被解释为int类型。
需要注意的是,C标准库提供了一个特定的头文件来定义布尔类型:stdbool.h

当然你没有...但有些人可能会这样想,特别是对于负数,这就是为什么 :) - pinkpanther
什么?你弄反了。true被计算为1,而false被计算为0。C语言不知道本地布尔类型,它们只是整数。 - djechlin
在C语言中,关系运算符和相等运算符的结果类型为int,其值为01。C确实有一个实际的布尔类型(_Bool),并且在<stdbool.h>中定义了一个宏bool,但这只是在C99中添加的,它没有改变运算符使用新类型的语义。 - Keith Thompson
截至1999年标准,C确实有本地布尔类型。它被称为“_Bool”,而<stdbool.h>#define bool _Bool - Keith Thompson
@KeithThompson,你说的1 == 1被解释为int是正确的。已编辑。 - Shoe

3
我们不知道TRUE的确切值是多少,编译器可能有自己的定义。因此,您提供的是使用编译器内部定义的方法。如果您有良好的编程习惯,则通常不需要这样做,但对于某些糟糕的编码风格可以避免问题,例如:
如果 ((a > b) == TRUE)
如果手动将TRUE定义为1,而内部值为另一个值,则可能会出现灾难。

在C语言中,>运算符总是返回1表示真,0表示假。没有任何C编译器会出现这种错误。将等式与TRUEFALSE进行比较是不好的风格;更清晰的写法是if (a > b)。但是,不同的C编译器可以以不同的方式处理真和假的想法是错误的。 - Keith Thompson

2
  1. 列表项

通常在C编程语言中,1被定义为真,0被定义为假。因此你经常会看到以下代码:

#define TRUE 1 
#define FALSE 0

然而,在条件语句中,任何不等于0的数字也会被视为true。因此,通过以下方式实现:

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

你可以明确表明你试图通过将false赋值为任何不为真的内容来保险起见。

4
我不会称之为“安全起见”,相反,你只是给自己一种虚假的安全感。 - dodgethesteamroller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接