linux 内核中有争议的 __set_task_state(tsk, state_value) 宏语法

Controversial __set_task_state(tsk, state_value) macro syntax in linux kernel

我在 linux kernel 2.4.31 文件中的几个宏中遇到过这种语法,但为了突出重点,我询问的是 include/linux/sched.h 中定义的 __set_task_state(tsk, state_value) 宏:

#define __set_task_state(tsk, state_value)      \
    do { (tsk)->state = (state_value); } while (0) 

do while(0) 语句定义上述宏的“优势”是什么?有什么副作用吗? 像这样定义它更方便:

#define __set_task_state(tsk, state_value)  (tsk)->state = (state_value) 

因为这个宏扩展到单行所以没有多大意义,但是有一个副作用,使用 do ... 版本使得表达式不能在两个方向上赋值:

state = __set_task_state(tsk, state_value); // syntax error in the do version


__set_task_state(tsk, state_value) = state; // syntax error in the do version

使用 do-while 而不是不使用它来定义它至少有两个原因。

首先,如果您在定义时不使用 do-while,那么宏可以用作 returns 值的函数。如果你不喜欢,你应该使用 do-while 包装器,即使是单语句宏。

第二个原因类似。如果您在没有包装器的情况下定义它,那么您可以使用它来赋值: set_task_state(my_task, state) = different_state;

这将是完全合法的,但对于阅读您的代码的任何人来说都非常混乱。它之所以有效,是因为它扩展为: (my_task)->state = (state) = different_state;

另一个原因是 if-else 语句。如果你像这样使用它而没有宏中的 do-while(0)

if (check)
  __set_task_state(tsk, state_value);
else
  do_something_else;

编译器会因为没有关联的“if”的“else”(例如孤立的“else”)而报错,因为宏扩展会将上面的代码翻译为“if”下没有大括号的两条指令:

if (check)
  (tsk)->state = (state_value);; <-- The double ";;" separates two instructions under the "if".
                                 <-- This makes the following "else" orphan
                                 <-- This requires to add braces to fix the compilation error
else
  do_something_else;

宏中的 do-while(0) 使分组指令显示为单个指令。这里的扩展是:

if (check)
  do { (tsk)->state = (state_value); } while(0); <-- Here, there is a single instruction under the "if"
                                                 <-- So the following "else" is syntactically correct
                                                 <-- No need to add braces
else
  do_something_else;

N.B.:此技巧通常用于将多个指令组合在一个宏中,使它们显示为单个指令:

#define MACRO() do { inst1; inst2; inst3;...; } while (0)

这使得以下工作无需大括号:

if (check)
  MACRO();
else
  do_something_else;

也避免了严重的隐藏错误,例如:

while(check)
  MACRO();

如果没有 do-while(0) 构造,只有第一条指令(即 inst1)会在 while(检查)循环!

在这个link, there are additional tips to avoid traps with the c-preprocessor. The above tip is described in §3.