内核代码中的 "do { ... } while (0)" 究竟是什么意思?

25

可能的重复:
当我们定义宏时,do while(0)有什么用处?
为什么C / C ++宏中有时会出现无意义的do / while和if / else语句?
C多行宏:do / while(0)vs作用域块

我之前看到过很多像这样的用法,以前我认为程序员想要轻松地跳出一段代码块。为什么我们在这里需要一个 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)

这是你的答案:http://kernelnewbies.org/FAQ/DoWhile0。 - David V.
我认为你是正确的。这会创建一个可以跳出的块。此外,它还会在堆栈上创建另一个框架,但在大多数情况下,它只会被优化掉。要获取更多线索,请查看ia64_*的定义。它们可能是具有break语句或其他类型的模拟的宏。 - Vlad
2
https://dev59.com/YHNA5IYBdhLWcg3wKai3 - S.Skov
你要么全部做完,要么一个也不做。 - Brian T Hannan
6个回答

30

它经常在宏中使用,这样在调用后需要加上分号,就像调用普通函数一样。

在您的示例中,您必须编写

__local_irq_save(1);

当......时

__local_irq_save(1)

如果使用do while将导致出现有关缺少分号的错误。如果没有do-while,则不会发生这种情况。如果只涉及作用域,则简单的花括号对就足够了。


+1 这是一个更好的解释。 - Steven Sudit
太棒了。这是一个很方便的技巧! - Toji
@OregonGhost: 当然,这是来自Linux内核。你有没有注意到Linus Torvalds对C++的看法? - David Thornley
@AndreyT:我的回答确实缺少了if,但既然Johannes Schaub已经写过了,我想我就不再添加了。正如我在另一条评论中所写的那样,我认为要求每个if都需要花括号比使用宏技巧更能产生更好的代码。请注意,即使在宏调用之后也需要分号,这使得阅读代码和IDE更容易,因为IDE通常期望在函数调用后有一个分号。 - OregonGhost
@OregonGhost:当然,如果可以使用内联函数,它们比宏更好。我的观点是,在这里遇到非标准C90的东西的可能性最小。这真的不是很重要。 - David Thornley
显示剩余6条评论

24

它允许代码在此处显示:

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

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

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

if(a) { ... }; else ...; 
否则将不再隶属于任何一个if语句,因为分号是下一条语句并且将else与前面的if语句分开。这将导致编译错误。

另一个有效的理由。另一方面,我总是在控制流语句中使用花括号,这样就不会发生这种情况。编译器错误对我来说是可以接受的。如果else与if分开而没有编译错误,那就更糟了 ;) - 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
@Michael Speer:不是这样的。如果__set_error使用{},那么在__set_error后面的;将终止两个if,而else将变成孤立的——语法错误。我听说过关于误关联else的潜在问题,但我想不出一个合适的例子。也许这并不是真正可能的,只是一个城市传说。有人知道吗? - AnT stands with Russia
@AndreyT,感谢您的纠正。我并不完全确定,也没有测试过。但这是有道理的。事实上,这就是标准的表述方式:“在 if 语句的第二种形式(包括 else 的形式)中,如果第一个子语句也是一个 if 语句,则该内部 if 语句必须包含一个 else 部分。”。 - Johannes Schaub - litb
@Michael Speer:你是个白痴。@AndreyT:感谢您的纠正。我不知道为什么在我输入时那样说话有意义。 - Michael Speer

11
do{ ... } while(0) 结构的目的是将一组语句转化为单个复合语句,可以以分号 ; 结尾。在 C 语言中,do/while 结构有一个奇怪和不寻常的属性:尽管它使用起来像一个复合语句,但它却期望在结尾处有一个 ;。C 中没有其他的复合结构具有这种属性。

由于这个属性,你可以使用 do/while 来编写多语句宏,这些宏可以安全地用作“普通”函数,而不必担心宏内部发生了什么,例如下面的示例:

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

5
答案已经给出(因此,当调用时,宏强制使用;),但我看到了另一种使用这种语句的方法:它允许在“循环”中的任何地方调用break,并在需要时提前终止。本质上是一个“goto”,你的同伴程序员不会因此而杀了你。
do {
    int i = do_something();
    if(i == 0) { break; } // Skips the remainder of the logic
    do_something_else();
} while(0);

请注意,这仍然相当令人困惑,因此我不鼓励使用。

2

它利用宏像实际语句或函数调用一样。

语句可以是 { 表达式列表 }表达式;,因此在定义需要多个表达式的宏时会遇到问题,因为如果使用 { },则如果宏的调用者很合理地在else之前添加一个;,就会发生语法错误。

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

如果f()是一个简单的宏,那就很好,但如果它是一个复杂的宏呢?你最终会得到if(...) { s1; s2; }; else ...这样的代码,而这是行不通的。因此,宏的编写者必须将其转换为真正的函数,将结构封装在单个语句中,或使用gnu扩展。 do .. while(0)模式是“封装结构”的方法。

2

看起来它只是用于作用域。 它类似于:

if (true)
{
    // Do stuff.
}

编辑

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


如果只是为了作用域,为什么还要有'do'和'while(0)'或者在这个答案中的'if(true)'呢?为什么不让开放和闭合的花括号自己站立呢? - semaj
答案是它不仅仅是用于作用域。AndreyT和其他人已经解释了其余部分。 - Steven Sudit

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