【问题标题】:What does "do { ... } while (0)" do exactly in kernel code? [duplicate]“do { ... } while (0)”在内核代码中究竟做了什么? [复制]
【发布时间】:2011-01-23 18:57:18
【问题描述】:

可能的重复:
What’s the use of do while(0) when we define a macro?
Why are there sometimes meaningless do/while and if/else statements in C/C++ macros?
C multi-line macro: do/while(0) vs scope block

我见过很多这样的用法,以前我认为程序员想轻松地跳出一段代码。为什么我们需要一个 do { ... } while (0) 循环?我们是想告诉编译器一些事情吗?

例如在 Linux 内核 2.6.25 中,include/asm-ia64/system.h

/*
 * - clearing psr.i is implicitly serialized (visible by next insn)
 * - setting psr.i requires data serialization
 * - we need a stop-bit before reading PSR because we sometimes
 *   write a floating-point register right before reading the PSR
 *   and that writes to PSR.mfl
 */
#define __local_irq_save(x)         \
do {                    \
    ia64_stop();                \
    (x) = ia64_getreg(_IA64_REG_PSR);   \
    ia64_stop();                \
    ia64_rsm(IA64_PSR_I);           \
} while (0)

【问题讨论】:

  • 我认为你是对的。这创造了你可以突破的障碍。它还在堆栈上创建另一个框架,但在大多数情况下,它只会被优化掉。如需更多线索,请查看 ia64_* 的定义。它们可能是具有 break 语句或其他类型的嘲弄的宏。
  • 要么全部做,要么不做

标签: c linux-kernel kernel


【解决方案1】:

它总是用在宏中,因此调用后需要分号,就像调用常规函数时一样。

在你的例子中,你必须写

__local_irq_save(1);

同时

__local_irq_save(1)

会导致缺少分号的错误。如果 do while 不存在,则不会发生这种情况。如果只是范围界定,一个简单的花括号对就足够了。

【讨论】:

  • +1 这样解释更好。
  • 令人着迷。这是一个方便的技巧!
  • 迷人,有点。不过,我会在任何时候(如果您使用 C++)建议在这样的宏上使用内联函数,这可以免费满足您的分号要求:)
  • @OregonGhost:当然,这是来自 Linux 内核。你注意到 Linus Torvalds 对 C++ 的看法了吗?
  • 我想说,如果没有 if 示例,它并不能真正解释 ; 的问题。我们可以使用普通的{},而; 将成为可选的。所以呢?那么如果我们忘记将; 放在不需要的地方怎么办?但是,一旦您看到if 的问题,就会清楚do/while 的技巧如何更好。
【解决方案2】:

它允许代码出现在这里:

if(a) __local_irq_save(x); else ...;

// -> if(a) do { .. } while(0); else ...;

如果他们只是使用 { .. } 你会得到

if(a) { ... }; else ...; 

else 不再属于任何if,因为分号将是下一个语句并将else 与前面的if 分开。会发生编译错误。

【讨论】:

  • 也是一个正当的理由。另一方面,我总是在控制流语句中使用花括号,这样就不会发生这种情况。因此,编译器错误对我来说是可以接受的。如果 else 与 if without 编译错误分开会更糟;)
  • OregonGhost:并不总是存在语法错误。 if( allowed ) if( ! appropriate ) __set_error(NOT_RIGHT_NOW); else __do_root_action( WIN ) ; 使用__set_error 宏使用do{...}while(0),这有效,如果您只使用{...},else 实际上附加到if(allowed) 而不是if(!appropriate)。哎呀。
  • @Michael Speer:不正确。如果__set_error 使用{},那么__set_error 之后的; 将终止if-s 和else 将成为孤立-语法错误。我听说了与错误关联的else 的潜在问题,但我想不出一个合适的例子。也许真的不可能,只是一个都市传说。有人吗?
  • @AndreyT,感谢您的纠正。我不完全确定,也没有测试它。但这是有道理的。事实上,标准是这样表述的:“在 if 语句的第二种形式(包括 else 的那个)中,如果第一个子语句也是 if 语句,那么内部 if 语句应包含 else 部分。”。跨度>
  • @Michael Speer:你是个白痴。 @AndreyT:感谢您的更正。我不知道为什么当我输入它时这对我来说是有意义的。
【解决方案3】:

do{ ... } while(0) 构造的目的是将一组语句转换为单个复合语句,可以用; 终止。您会看到,在 C 语言中,do/while 构造具有一个奇怪且不寻常的属性:即使它作为复合语句“工作”,它也期望在结尾处出现 ;。 C 中没有其他复合结构具有此属性。

由于这个属性,你可以使用do/while来编写多语句宏,它可以安全地用作“普通”函数,而不用担心宏里面有什么,如下例所示

if (/* some condition */)
  __local_irq_save(x); /* <- we can safely put `;` here */
else
  /* whatever */;

【讨论】:

    【解决方案4】:

    答案已经给出(所以宏在调用时强制使用;),但是我见过这种语句的另一种用法:它允许在“循环”中的任何地方调用break,提前终止如果需要的话。本质上是你的程序员同事不会谋杀你的“goto”。

    do {
        int i = do_something();
        if(i == 0) { break; } // Skips the remainder of the logic
        do_something_else();
    } while(0);
    

    请注意,这仍然相当混乱,因此我不鼓励使用它。

    【讨论】:

      【解决方案5】:

      看起来它只是为了确定范围。类似于:

      if (true)
      {
          // Do stuff.
      }
      

      编辑

      我在您的示例中没有看到它,但其中一个函数调用可能实际上是一个宏,在这种情况下,do/while(0) 和 if(true) 之间有一个关键区别,那就是前者允许continuebreak

      【讨论】:

      • 如果它只是用于范围界定,为什么还要有'do'和'while (0)'或者在这个答案的情况下'if (true)'?为什么不让左花括号和右花括号独立存在?
      • 答案是,它不仅仅是 用于范围界定。 AndreyT 和其他人已经解释了其余部分。
      【解决方案6】:

      它使用宏就像一个真正的语句或函数调用。

      一个语句要么是{ expression-list }要么是expression;,所以在定义需要多个表达式的宏时会出现问题,因为如果你使用{ },那么如果宏的调用者相当合理,就会出现语法错误在 else 之前添加 ;

      if(whatever)
        f(x);
      else
        f(y);
      

      如果f() 是一个单语句宏,那很好,但如果它是一个宏并且有些复杂怎么办?你最终得到了if(...) { s1; s2; }; else ...,但这是行不通的。

      因此,宏的编写者必须要么将其变成一个真正的函数,要么将构造包装在单个语句中,要么使用 gnu 扩展。

      do .. while(0) 模式是“包装构造”方法。

      【讨论】:

        猜你喜欢
        • 2019-03-06
        • 1970-01-01
        • 1970-01-01
        • 2011-05-09
        • 2018-03-23
        • 2019-02-15
        • 2017-06-11
        • 2015-09-15
        相关资源
        最近更新 更多