ChatGPT解决这个技术问题 Extra ChatGPT

if-else 块中的“if (0)”块的目的是什么?

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

整体代码如下所示:

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)”用法本身。

这种说法似乎无关紧要。生成带有和不带有该语句的汇编代码,您将看到引擎盖下发生了什么。
这可能是自动生成的代码。

C
CSM

如果有 #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) {} 部分。一个简单的自动生成器可以生成这样的代码,因为它更容易编码 - 它不必单独考虑第一个启用的块。


在许多情况下,if/else if 链不像决策树那样使用,而是作为“根据第一个匹配条件采取行动”的构造,其中恰好具有最高优先级的条件是t 特别“特别”。虽然我没有看到 if(0) 被用作允许所有真实分支具有一致语法的一种方式,但我喜欢它所促进的一致语法。
在这种情况下它甚至没有用,因为您可以在没有的情况下实现相同的效果:只需将 else if 行分成两部分并将预处理器保护放在中间。
@KonradRudolph 我没有关注;你会怎么写?
@JiK 我会删除 if (0) 分支并重新格式化其余的分支,使 else 在自己的线上,并被沿 #if TEST1_ENABLED && TEST2_ENABLED 线的守卫包围。
@KonradRudolph 如果您想将警卫人数增加一倍并将提到的警卫条件数量增加三倍,那很好,我想。
P
PSkocik

我有时将它用于对称,因此我可以用我的编辑器自由移动另一个 else if{,而不必介意第一个 if

语义上

if (0) {
    // Empty braces
} else 

部分不做任何事情,您可以指望优化器将其删除。


个人观点:虽然这可能是代码按原样编写的原因,但我认为这是一个不好的理由。代码的阅读次数多于编写次数,而这种不必要的代码只会增加阅读器的解析开销。
@user694733:您可能会争辩说,所有重要代码路径的通用 if else 前缀很好地排列了条件并使扫描它们更容易。 (不过,这是主观的,并且取决于条件和代码块中的很多内容。)
我认为 if (0) {..} 不会引入任何可解析性/可读性问题。任何懂一点 C 的人都应该明白这一点。这不是问题。问题是阅读后的后续问题:“那到底是为了什么?”除非是出于调试/临时目的(即,目的是稍后“启用”该 if 块),否则我会提倡完全删除。基本上“阅读”这样的代码可能会无缘无故地给读者造成不必要的“暂停”。这是一个足够好的理由来删除它。
似乎它肯定会降低可读性。太糟糕了,它派那个程序员去 SO 询问它的用途。不是一个好兆头。
即使使用这种模式,我也不知道您是否可以“无忧无虑地在编辑器中移动 else if”,因为条件可能不是互斥的,在这种情况下 order 很重要。我个人只会使用 if,并执行 early return,如有必要,将逻辑链提取到单独的函数中。
S
Seth Flowers

我在生成的代码中看到了类似的模式。例如,在 SQL 中,我看到库发出以下 where 子句。

where 1 = 1

这大概使添加其他条件变得更容易,因为所有其他条件都可以在前面加上 and,而不是额外检查它是否是第一个条件。


1=1 也是“有用的”,因为您始终可以无条件地将 where 添加到前面。否则,您必须检查它是否为空,如果是,请避免生成 where 子句。
此外,大多数数据库会自动从 WHERE 中“删除”1=1,因此不会影响性能。
这在自动生成 SQL 查询的库中是可以接受的,即使 DevOps 团队也很可能从未见过。在必须多次写入和读取的高级代码中,这是不“可接受的”。
当生成某种具有未知数量最终条件的动态 SQL 时,这是一种非常方便的方法。
@freakish 确实我写了相反的东西:在生成的代码中可读性差的语法是可以接受的,因为它很可能永远不会被读取,而不是在由开发人员维护的高级功能代码中。
R
Russell Borogove

如所写,if (0) {} 子句编译为空。

我怀疑这个阶梯顶部的子句的功能是提供一个简单的地方,通过将 0 更改为 1true 来一次性暂时禁用所有其他功能(用于调试或比较目的) .


搞定了。除了调试之外,我看不到任何其他原因。
s
studog

一种尚未提及的可能性:if (0) { 行可能为断点提供了一个方便的位置。

调试通常是在未优化的代码上进行的,因此总是错误的测试将存在并且能够在其上设置断点。当为生产编译时,代码行将被优化。看似无用的行为开发和测试构建提供了功能,而不会影响发布构建。

上面还有其他很好的建议;真正知道目的是什么的唯一方法是追踪作者并询问。您的源代码控制系统可能会对此有所帮助。 (寻找 blame 类型的功能。)


P
Peter Mortensen

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

发生这种情况是因为一些代码修改,其中一个主要条件被删除(假设在初始 if 块中的函数调用),但是开发人员/维护人员

懒得重组 if-else 块

不想降低分支覆盖率

因此,他们没有删除关联的 if 块,而是简单地将条件更改为 if(0) 并继续前进。


if(0) 是不是也减少了分支覆盖率?
@DavidSzalai 不完全 - 最多会减少 1 (从之前的 2 次) - 但据我所知,覆盖仍然需要一次命中。
D
Dark Matter

这是代码腐烂。

在某些时候,“如果”做了一些有用的事情,情况发生了变化,也许被评估的变量被删除了。

修复/更改系统的人尽可能少地影响系统的逻辑,所以他只是确保代码会重新编译。所以他留下了一个“if(0)”,因为这既快速又简单,而且他不完全确定这是他想要做的。他让系统正常工作,他不会回去完全修复它。

然后下一个开发人员出现并认为这是故意完成的并且只注释掉那部分代码(因为它无论如何都没有被评估),然后在下次触及代码时这些注释被删除。


是的。对于古老的代码,一次做一个删除死代码的更改。我数不清有多少次我对“死”代码进行刀耕火种,却发现刀耕火种遗漏了一些奇怪的副作用。
s
simonarame

我在使用模板语言生成的预扩展 JavaScript 中看到了不可访问的代码块。

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

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

一旦预编译,因此:

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

希望这可以帮助您了解我表现出热情的亲回收编码器时代潜在的低键盘活动!


我同意,在自动化无处不在的时代,我们应该更多地依赖自动生成的代码,因为它可以让我们把更多的时间花在实际的事情上。但就目前而言,我的确切兴趣点是这一切是如何在引擎盖下构建的。
p
philfr

该构造也可以在 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

c
cha0site

我认为这只是糟糕的代码。在 Compiler Explorer 中编写一个快速示例,我们看到在 gcc 和 clang 中都没有为 if (0) 块生成代码,即使完全禁用了优化:

https://godbolt.org/z/PETIks

尝试删除 if (0) 不会对生成的代码进行任何更改,因此我得出结论,这不是优化。

顶部 if 块中可能曾经有一些东西后来被删除了。简而言之,删除它似乎会导致生成完全相同的代码,所以请随意这样做。


s
sergiopm

如前所述,零被评估为假,编译器可能会优化分支。

我之前在代码中也看到过这一点,其中添加了一个新功能并且需要一个终止开关(如果该功能出现问题,您可以将其关闭),一段时间后,当终止开关被删除时程序员也没有删除分支,例如

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

变成了

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

K
Krazy Glew

@PSkocik 的回答很好,但我加了两分钱。不确定我应该将其作为评论还是作为答案;选择后者,因为恕我直言,值得其他人看到,而评论通常是不可见的。

我不仅偶尔使用

if(0) {
   //deliberately left empty
} else if( cond1 ) {
   //deliberately left empty
} else if( cond2 ) {
   //deliberately left empty
...
} else {
   // no conditions matched
}

但我也偶尔会

if( 1 
    && cond1 
    && cond2
    ...
    && condN
) {

或者

if( 0 
    || cond1 
    || cond2
    ...
    || condN
) {

对于复杂的条件。出于同样的原因 - 更易于编辑、#ifdef 等。

就此而言,在 Perl 我会做

@array = (  
    elem1,
    elem2,
    ...
    elem1,
) {

注意列表末尾的逗号。我忘记了逗号是 C 和 C++ 列表中的分隔符还是定界符。恕我直言,这是我们学到的一件事:[Perl 中的尾随逗号是一种不好的做法吗?逗号] 是个好东西。像任何新符号一样,需要一段时间才能习惯。

我将 if(0) 代码与 lisp 进行比较

(cond   (test1    action1)
   (test2    action2)
   ...
   (testn   actionn))

你猜对了,我可以缩进为

(cond   
   (test1    action1)
   (test2    action2)
   ...
   (testn   actionn)
)

我有时会尝试想象一种更易于人类阅读的语法可能是什么样子。

也许

IF
:: cond1 THEN code1
:: cond2 THEN code2
...
:: condN THEN codeN
FI

受到 Dikstra 的 [https://en.wikipedia.org/wiki/Guarded_Command_Language#Selection:_if][Guarded 命令语言] 的启发。

但是这种语法意味着条件是并行评估的,而 if...else-if 意味着条件的顺序和优先级评估。

我在编写生成其他程序的程序时开始做这种事情,这特别方便。

当我们这样做时,在使用英特尔的旧 iHDL 编写 RTL 时,我编写了类似的代码

   IF 0 THEN /*nothing*/
   **FORC i FROM 1 TO 10 DOC** 
   ELSE IF signal%i% THEN    
      // stuff to do if signal%i% is active
   **ENDC** 
   ELSE   
      // nothing matched 
   ENDIF

其中 FORC..DOC..ENDC 是一个宏预处理器循环构造,它扩展为

   IF 0 THEN /*nothing*/
   ELSE IF signal1 THEN    
      // stuff to do if signal1 is active
   ELSE IF signal2 THEN    
      // stuff to do if signal2 is active
   ...
   ELSE IF signal100 THEN    
      // stuff to do if signal100 is active
   ELSE   
      // nothing matched 
   ENDIF

这是单一赋值、非命令式代码,因此如果您需要执行查找第一个设置位之类的操作,则不允许设置状态变量。

   IF 0 THEN /*nothing*/
   ELSE IF signal1 THEN    
      found := 1
   ELSE IF signal2 THEN    
      found := 2
   ...
   ELSE IF signal100 THEN    
      found := 100
   ELSE   
      // nothing matched 
   ENDIF

想想看,这可能是我第一次遇到这种构造的地方。

顺便说一句,有些人对 if(0) 样式的反对意见——else-if 条件是顺序相关的,不能任意重新排序——不适用于 RTL 中的 AND 和 OR 和 XOR 逻辑——但确实适用于 short-电路 && 和 ||。


A
Abdul Ahad Sheikh

仅放置 if 块 1 有助于调试此块。这将禁用所有 if else 块功能。我们也可以扩展 if else 块。


S
Sagar Kumar Choudhury
    Actually according to my opinion, if we put any variable for checking inside
    e.g:-
public static void main(string args[])
{
        var status;
        var empList=_unitofWork.EmpRepository.Get(con=>con.isRetired==true);
        //some code logic 
        if(empList.count>0)
        {
          status=true;
        }
        if(status)
        {
         //do something
        }
        else
        {
        //do something else
        }
}
     if then its dynamically get the value in run time and invoke the logic inside it, else its simply extra line of code i guess.

    Anybody have any depth knowledge why this thing is used....or agree with me.
    kindly respond. 

J
Jitesh Prajapati

例如,我已经看到这用于处理错误

if(0){
lable1:
   //do something
}
if(0){
lable2:
   //do something
}
.
.
and so on.

if(condition_fails)
   goto lable1;

当 goto 用于管理错误时,这会很有帮助,语句仅在发生错误时执行。我在非常古老的 C 代码中看到了这一点(其中函数参数写在 '()' 之外),现在不要认为有人遵循这个。


J
John U

我已经看过几次了,我认为最可能的原因是它正在评估代码的旧/不同版本/分支中的某些内容,或者可能用于调试,将其更改为 if(0) 是一种有点懒惰的删除方式不管那里有什么。