在if-else块中,'if(0)'块的目的是什么?

144

我的问题是关于我在标题中提到的那行代码,它在生产代码的许多地方都可以看到。

整个代码看起来像这样:

if (0) {
    // Empty braces
} else if (some_fn_call()) {
    // actual code
} else if (some_other_fn_call()) {
    // another actual code
    ...
} else {
    // default case
}

其他分支与我的问题无关。我想知道在这里放置if (0)的含义是什么。花括号为空,所以我认为它不应该注释掉某个代码块。它是强制编译器进行一些优化还是其意图不同?

我尝试在SO和互联网上搜索此显式案例,但没有成功。有关JavaScript的类似问题,但没有C语言的问题。还有另一个问题What happens when a zero is assigned in an `if` condition?,但是它讨论的是将零赋值给变量,而不是'if (0)'本身的用法。


2
那个语句似乎不相关。生成带有和不带有该语句的汇编代码,你就会看到引擎内部发生了什么。 - haccks
2
这可能是一段自动生成的代码。 - freakish
16个回答

106

如果存在#if语句,那么这将非常有用,ala

   if (0)
   {
       // Empty block
   }
#if TEST1_ENABLED
   else if (test1())
   {
      action1();
   }
#endif
#if TEST2_ENABLED
   else if (test2())
   {
      action2();
   }
#endif

在这种情况下,任何一个测试都可以用#if语句注释掉,代码将能够正确编译。几乎所有的编译器都会删除if (0) {}部分。 一个简单的自动生成器可以生成类似这样的代码,它稍微容易编写-它不必单独考虑第一个启用的块。


5
在许多情况下,if/else if 这一代码结构并不是作为一个决策树使用,而是作为一个“在第一个匹配条件上执行操作”的结构,其中具有最高优先级的条件并不特别“特殊”。虽然我之前没有见过 if(0) 作为一种允许所有实际分支具有一致语法的方式,但我喜欢它能够促进一致的语法。 - supercat
1
在这种情况下,它甚至没有用处,因为您可以通过将“else if”行拆分为两个部分并在中间放置预处理器保护来实现相同的效果。 - Konrad Rudolph
1
@KonradRudolph 我不太明白,你会怎么写呢? - JiK
1
@JiK 我会删除 if (0) 分支,并重新格式化其余部分,使得 else 单独成行,并且被类似于 #if TEST1_ENABLED && TEST2_ENABLED 的守卫所包围。 - Konrad Rudolph
5
@KonradRudolph,如果您想要加倍守卫的数量并且三倍提及守卫条件,那也可以。 - hobbs
显示剩余2条评论

92

有时我使用它来实现对称,这样我就可以在编辑器中自由移动其他的else if{而不必考虑第一个if

从语义上讲

if (0) {
    // Empty braces
} else 

这部分没有任何作用,您可以依靠优化器将其删除。


240
个人意见:虽然这可能是编写代码的原因,但我认为这是一个糟糕的理由。代码的阅读次数比编写次数更多,而这种不必要的代码只会增加读者解析的负担。 - user694733
13
你可以认为,在所有重要的代码路径中,常见的 if else 前缀将条件排列得整齐,并使其更易于扫描。这是主观的,取决于条件和代码块内部实际包含的内容。 - M Oehm
72
我认为if (0) {..}并不会引入任何可读性/可解析性问题,对于了解一点C语言的人来说应该是显而易见的。这并不是问题所在。问题在于阅读后的后续问题:“那么它到底有什么用呢?”除非它是用于调试/临时目的(即,意图稍后“启用”该if块),否则我建议完全删除它。基本上“阅读”这样的代码可能会导致读者不必要地“停顿”,而没有充分的理由。这已经足够的理由去除它了。 - P.P
77
看起来这绝对会降低可读性。它的质量如此之差,以至于那位程序员不得不去SO求助它的作用是什么。这不是一个好兆头。 - Vectorjohn
26
即使使用这个模式,我不知道是否可以“在编辑器中放心移动else if”,因为条件可能不是互斥的,这种情况下顺序很重要。个人而言,我会仅使用if并执行“提前返回”操作(Early Return),如果需要,将逻辑链提取到单独的函数中。 - John Wu
显示剩余16条评论

45

我曾经见过类似的模式在生成的代码中使用过。例如,在SQL中,我看到库会发出以下where子句。

where 1 = 1

这可能使添加其他条件更容易,因为所有附加条件都可以使用 and 前置,而不需要额外检查它是否是第一个条件。


4
1=1也是“有用”的,因为你总是可以无条件地在它前面添加where。否则,你就必须检查它是否为空,如果是,则避免生成where子句。 - Bakuriu
2
此外,大多数数据库将自动从WHERE子句中“删除” 1=1,因此它不会影响性能。 - anon
7
在自动生成SQL查询的图书馆中,这是可以接受的,即使DevOps团队很可能从未看过这些查询。但在需要多次编写和阅读的高级代码中,这是不可接受的。 - phagio
当生成一些具有未知最终条件的动态SQL时,这是非常方便的方法。 - Skipper
1
@freakish 确实,我写了相反的意思:在生成的代码中,难以阅读的语法是可以接受的,因为它很可能永远不会被阅读,但在由开发人员维护的高级功能代码中则不可接受。 - phagio
显示剩余4条评论

44

如此编写的 if (0) {} 子句在编译时会被优化掉。

我猜测这个梯子顶部的子句的作用是提供一个方便的地方,可以通过将 0 更改为 1true 以一次性禁用所有其他功能(用于调试或比较)。


2
做得好。我除了调试之外,看不出任何其他原因。 - tfont

16

我不确定是否有任何优化,但是我的两分钱:

这种情况发生是因为进行了一些代码修改,其中一个主要条件被删除了(例如最初的 if 块中的函数调用),但开发人员/维护人员:

所以他们没有移除相关的 if 块,而是将条件更改为 if(0) 并继续工作。


3
if(0)难道不也会降低分支覆盖率吗? - David Szalai
1
@DavidSzalai 不完全准确 - 最多只会减少1(从之前的2)- 但据我所知,仍需要一个命中以实现覆盖。 - Sourav Ghosh

16

还有一个可能性未被提及: if (0) { 这一行代码可能为断点设置提供了一个便捷的位置。

调试通常在非优化代码上进行,因此始终为假的测试将存在并能够设置断点。当编译为生产环境时,这行代码将被优化掉。这看似无用的代码行为开发和测试版本提供了功能,在发布版本上没有任何影响。

还有其他好的建议,但要真正了解其目的,就必须追踪作者并询问。您的源代码控制系统可能会帮助您实现这一点。(寻找类似“blame”的功能)。


15

这是代码腐败。

在某个时刻,“if”语句曾经有用,但情况发生了变化,也许被评估的变量已被移除。

修复或更改系统的人尽可能地不影响系统逻辑,所以他只确保代码可以重新编译,就留下了“if(0)” ,因为这很快速和简单,并且他并不完全确定自己想要做什么。他让系统正常工作,并且没有回去完全修复它。

接下来的开发者认为那是故意这样做的,只注释掉代码中的那部分内容(因为无论如何都不会被评估),然后下次修改代码时就会删除这些注释。


2
没错。对于古老的代码,一次只进行一个死代码删除更改。我数不清有多少次我对“死”代码进行了斩草除根的攻击,结果发现有一些奇怪的副作用被忽略了。 - Julie in Austin

9

我曾经看到过使用模板语言生成的预展开JavaScript代码中的不可达代码块。

例如,您正在阅读的代码可能是从服务器粘贴过来的,在那时,第一个条件依赖于仅在服务器端可用的变量已被预先评估。

if ( ${requestIsNotHttps} ){ ... }else if( ...

预编译后,这意味着:

if ( 0 ){ ... }else if ( ...

希望这可以帮助您相对化潜在的键盘低活跃度,这是我表现出热情的支持回收编码人员时代的方式!

1
我同意,在无处不在的自动化时代,我们应该更多地依赖自动生成的代码,因为它使我们能够将更多时间花在实际事物上。但目前,我的确切兴趣点是如何在幕后构建这一切。 - Zzaponka

8

该结构在C语言中也可以用于实现具有类型安全性的泛型编程,这是因为编译器仍会检查无法到达的代码:

// this is a generic unsafe function, that will call fun(arg) at a later time
void defer(void *fun, void *arg);

// this is a macro that makes it safer, by checking the argument
// matches the function signature
#define DEFER(f, arg) \
   if(0) f(arg); \              // never actually called, but compile-time checked
   else defer(f, (void *)arg);  // do the unsafe call after safety check

void myfunction(int *p);

DEFER(myfunction, 42);     // compile error
int *b;
DEFER(myfunction, b);      // compiles OK

6

正如所说,0会被评估为false,并且分支很可能会被编译器优化掉。

我以前在代码中也见过这种情况,当添加了新功能并需要一个开关时(如果功能出现问题,可以关闭它),一段时间后,当开关被移除时,程序员没有同时删除分支,例如:

if (feature_a_active()) {
    use_feature_a();
} else if (some_fn()) {
   ...

成为
if (0) {
   // empty
} else if (some_fn()) {
   ...

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