【问题标题】:Why #define TRUE (1==1) in a C boolean macro instead of simply as 1?为什么在 C 布尔宏中使用 #define TRUE (1==1) 而不是简单地为 1?
【发布时间】:2013-06-09 13:22:23
【问题描述】:

我见过 C 中的定义

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

这有必要吗?与简单地将 TRUE 定义为 1,将 FALSE 定义为 0 相比,有什么好处?

【问题讨论】:

  • 还有更多:#define TRUE (’/’/’/’); #define FALSE (’-’-’-’)(取自 coding-guidelines.com/cbook/cbook1_1.pdf 第 871 页)
  • 不,这是无知的妄想症^Wunderinformed,真的。在 C 中,1 和 0 在所有情况下都是一样的。
  • @osgx 是什么意思?

标签: c precompiler


【解决方案1】:

如果编译器支持,这种方法将使用实际的boolean 类型(并解析为truefalse)。 (特别是 C++)

但是,最好检查 C++ 是否正在使用(通过__cplusplus 宏)并实际使用truefalse

在 C 编译器中,这相当于 01
(请注意,由于操作顺序,删除括号会破坏它)

【讨论】:

  • 不正确,这里没有使用布尔值。 1==1 的结果是 int。 (见stackoverflow.com/questions/7687403/…。)
  • @Mat:即使在 C++ 中,使用 boolean 类型?
  • 问题被标记为 C,但实际上在 C++ 中,关系运算符返回 truefalse
  • @Mat:我猜这样的代码是用 C 头文件编写的,以便与 C++ 很好地配合
  • @SLaks 如果它想与 C++ 一起玩得很好,它会在定义 __cplusplus 时使用 #define TRUE true#define FALSE false
【解决方案2】:

答案是便携性。 TRUEFALSE 的数值并不重要。 重要的是,像 if (1 < 2) 这样的语句的计算结果为 if (TRUE),而像 if (1 > 2) 这样的语句的计算结果为 if (FALSE)

当然,在 C 中,(1 < 2) 的计算结果为 1(1 > 2) 的计算结果为 0,因此正如其他人所说,就编译器而言,没有实际区别。但是,通过让编译器根据自己的规则定义 TRUEFALSE,您可以使它们的含义对程序员明确,并且您可以保证程序和任何其他库的一致性(假设其他库遵循 C 标准...您会感到惊讶)。


一些历史
一些 BASIC 将 FALSE 定义为 0TRUE 定义为 -1。与许多现代语言一样,它们解释任何非零值为TRUE,但它们评估布尔表达式为-1。他们的NOT 操作是通过加 1 和翻转符号来实现的,因为这样做很有效。所以'NOT x'变成了-(x+1)。这样做的副作用是,像 5 这样的值计算为 TRUE,但 NOT 5 计算为 -6,这也是 TRUE!发现这种错误并不好玩。

最佳做法
鉴于 事实上 规则将零解释为 FALSE任何 非零值被解释为 TRUE,您应该永远不要比较布尔值TRUEFALSE 的表达式。例子:

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

为什么?因为很多程序员使用把ints 当作bools 对待的捷径。它们不一样,但编译器通常允许这样做。因此,例如,写是完全合法的

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

看起来是合法的,编译器会很乐意接受它,但它可能不会做你想要的。那是因为strcmp()的返回值是

     0 如果yourString == myString
yourString < myString
>0 如果yourString &gt; myString

所以上面的行只有在yourString &gt; 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.

您经常会在生产代码中发现其中一些“糟糕的示例”,许多有经验的程序员都对它们发誓:它们可以工作,有些比他们(迂腐?)正确的替代方案要短,而且这些习语几乎得到了普遍认可。但请考虑:“正确”版本的效率同样不低,它们保证可移植,甚至可以通过最严格的 linter,甚至新程序员也能理解它们。

这不值得吗?

【讨论】:

  • (1==1) 并不比 1 更便携。编译器自己的规则是 C 语言的规则,它对等式和关系运算符的语义是清晰明确的。我从未见过编译器会出错。
  • 实际上strcmp返回的值已知小于,等于或大于0。不保证为-1、0或1,野外有平台不'不要返回这些值以获得实施速度。所以如果strcmp(a, b) == TRUE 那么a &gt; b 但反向暗示可能不成立。
  • @KeithThompson - 也许“可移植性”是错误的术语。但事实仍然是 (1==1) 是一个布尔值; 1 不是。
  • @AdamLiss:在 C 中,(1==1)1 都是 int 类型的常量表达式,值为 1。它们在语义上是相同的。我想你可以编写迎合不知道这一点的读者的代码,但它在哪里结束?
  • 'not' 5 在位级别上确实是 -6。
【解决方案3】:

(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++ 中。

如何更好地集成的一个示例是,例如,如果函数 foo 具有 intbool 的重载,则 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

话虽如此,0==0 技巧已(是?)被一些程序员使用,即使在从未打算以任何方式与 C++ 互操作的代码中也是如此。 这没有任何意义,表明程序员对布尔值在 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

关于 cmets 的问题,重载 C++ 函数如何与混合 C 和 C++ 编程相关。这些只是说明了类型差异。当编译为 C++ 时,希望 true 常量为 bool 的正当理由是为了干净的诊断。在最高警告级别,如果我们将整数作为bool 参数传递,C++ 编译器可能会警告我们转换。用 Clean C 编写的一个原因不仅是我们的代码更便携(因为它可以被 C++ 编译器理解,而不仅仅是 C 编译器),而且我们可以从 C++ 编译器的诊断意见中受益。

【讨论】:

  • 优秀的,但被低估的答案。 TRUE 的两个定义在 C++ 下会有所不同,这一点并不明显。
  • 重载函数与编译为 C 和 C++ 的代码有何关系?
  • @KeithThompson 这不仅仅是关于重载,而是关于一般的正确输入,重载只是最实际的例子。当然,没有重载、模板和为 "C-compatibility" 删除的所有“复杂”内容的 C++ 代码根本不关心类型,但这并不意味着应该推翻给定语言的概念类型限制。
  • @ChristianRau:“不太关心类型”是什么意思?类型是 C 语言的核心; C 程序中的每个表达式、值和对象都有明确定义的类型。如果您想在 C 和 C++ 中定义不同的东西(在罕见的情况下,您实际上需要编写同时编译为 C 和 C++ 的代码),您可以使用 #ifdef __cplusplus 来表达您的意图更清楚。
  • @KeithThompson 是的,我知道类型有多重要。只是没有像重载和模板这样的所有类型感知的东西,boolint 之间的区别在实践中并不重要,因为它们可以相互隐式转换(并且在 C 实际上是“相同的”,但请注意引号),并且您真正需要在两者之间消除歧义 的情况并不多。 “不多” 可能太重了,“与使用模板和重载的代码相比要少得多” 可能会更好。
【解决方案4】:
#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,常量布尔值):

对于 C,TRUE 应定义为 1。然而,其他语言使用的数量不是 1,所以一些程序员认为!0 是安全的。

同样在 C99 中,布尔宏 truefalsestdbool.h 定义直接使用文字:

#define true   1
#define false  0

【讨论】:

  • 我有一个疑问,在每次使用时 TRUE 都会被替换为 1==1,而仅使用 1 将替换 1,这不是第一种方法额外的比较开销......或者它已被优化编译器?
  • @pinkpanther 常量表达式通常在编译时进行评估,因此不会产生任何开销。
  • 1==1 保证被评估为1
  • @NikosC。这是个好问题。这对于 if(foo == true) 形式的代码很重要,这将从糟糕的实践变成完全错误的错误。
  • +1 用于指出(x == TRUE) 中的危险可能与x 具有不同的真值。
【解决方案5】:

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

显然 C 表示它们是兼容的,但您可以选择禁止故意使用该功能来帮助突出错误 - 例如,有人可能混淆了 &amp;&amp;&amp;,或者他们搞砸了他们的运算符优先级。

【讨论】:

  • 这很好,也许其中一些工具甚至可以通过扩展为if (boolean_var == (1 == 1)) 来捕获愚蠢的代码if (boolean_var == TRUE),这要归功于(1 == 1) 节点的增强类型信息落入模式if (&lt;*&gt; == &lt;boolean_expr&gt;).
【解决方案6】:

实际的区别是没有的。 0 被评估为false1 被评估为true。您使用布尔表达式 (1 == 1) 或1 来定义true 的事实没有任何区别。他们都被评估为int

请注意,C 标准库提供了用于定义布尔值的特定标头:stdbool.h

【讨论】:

  • 当然你没有......但有些人可能会不这么认为,尤其是对于负数,这就是为什么:)
  • 什么?你倒过来了。 true 被评估为1false 被评估为0。 C 不知道原生布尔类型,它们只是整数。
  • 在 C 中,关系运算符和相等运算符产生 int 类型的结果,值为 01。 C 确实有一个实际的布尔类型(_Bool,在&lt;stdbool.h&gt; 中定义了一个宏bool,但这只是在C99 中添加的,它没有改变要使用的运算符的语义新类型。
  • @djechlin:从 1999 年标准开始,C 确实 具有原生布尔类型。它叫做_Bool,而&lt;stdbool.h&gt;#define bool _Bool
  • @KeithThompson,你是对的,1 == 1 被评估为int。已编辑。
【解决方案7】:

我们不知道 TRUE 的确切值,编译器可以有自己的定义。所以你提供的是使用编译器的内部定义。如果您有良好的编程习惯但可以避免一些不良编码风格的问题,这并不总是必要的,例如:

如果 ((a > b) == TRUE)

如果您手动将 TRUE 定义为 1,而 TRUE 的内部值是另一个,这可能是一场灾难。

【讨论】:

  • 在 C 中,&gt; 运算符总是产生 1 表示真,0 表示假。任何 C 编译器都不可能出错。与TRUEFALSE 的平等比较是糟糕的风格;以上更清楚地写为if (a &gt; b)。但是,不同的 C 编译器可以区别对待真假的想法是不正确的。
【解决方案8】:
  1. 列表项

通常在 C 编程语言中,1 定义为真,0 定义为假。因此,为什么您经常看到以下内容:

#define TRUE 1 
#define FALSE 0

但是,任何不等于 0 的数字在条件语句中也会被评估为真。因此,通过使用以下内容:

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

您可以通过使 false 等于任何不正确的内容来明确表明您试图安全行事。

【讨论】:

  • 我不会称之为“安全行事”——相反,你给自己一种虚假的安全感。
猜你喜欢
  • 2016-05-21
  • 2013-06-26
  • 2012-01-30
  • 2013-05-23
  • 2021-07-04
  • 1970-01-01
  • 2022-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多