如何在C++中实现可移植的空语句?

43

在C++中,偶尔需要使用一个空语句。例如,在实现assert()时,在非调试配置下禁用它(也请参见此问题):

#ifdef _DEBUG
#define assert(x) if( !x ) { \
                     ThrowExcepion(__FILE__, __LINE__);\
                  } else {\
                     //noop here \
                  }
#else
#define assert(x) //noop here
#endif

到目前为止,我认为正确的方法是使用(void)0;进行无操作:

(void)0;

但是我怀疑它可能会在某些编译器上触发警告 - 例如C4555:表达式没有效果; 预期具有副作用的表达式,在这种特定情况下不会发出Visual C ++警告,但在没有向void进行转换时会发出警告。

它是否普适? 有更好的方法吗?


通常而言,使用基于 DEBUG/RELEASE 的宏来改变程序行为并不是一个好主意... 这样可能会导致调试版本(易于操作)能正常运行,但发布版本却不能。至于无操作(no-op),可以使用 ; 来实现,(void)0;(你的宏中不应包含 ;,这应由调用者自行添加)。 - David Rodríguez - dribeas
@dribeas - David Rodríguez:是的,我知道,但在非调试版本中禁用断言是一种普遍做法,我只是举个例子。 - sharptooth
7
我不明白为什么你需要在一个原本为空的else块中加入一个no-op(空操作)。如果你想以后再填充它,可以将else块保持为空。 - ziu
1
@ziu 更多地是在谈论 #define assert(x) //noop here - AusCBloke
1
@sston - 这并不含糊。C语法规定else与最内层的if相绑定。如果您添加了不正确的缩进,它看起来可能会使人感到含糊不清,但事实并非如此。(一个具有足够高警告设置的编译器可能会声称它是模棱两可的,但在这种情况下,最好的解决方案是将编译器警告调整为您想要编写的代码,或者使用 do { if(!x) ThrowException(__FILE__, __LINE__); } while(0)。) - Chris Lutz
显示剩余5条评论
12个回答

27

最简单的空操作是不写任何代码:

#define noop

那么用户代码将会有:

if (condition) noop; else do_something();

你提到的另一种选择也是一个无操作:(void)0;,但如果你打算在宏中使用它,你应该将;留给调用者添加:

#define noop (void)0
if (condition) noop; else do_something();

(如果;是宏的一部分,那么那里会有一个额外的;)


23
我怀疑它可能会触发一些编译器的警告。
不太可能,因为当定义了 NDEBUG 时,标准 assert 宏所展开的内容就是 ((void)0)。因此,任何一个会为其发出警告的编译器都会在编译包含断言的代码时发出警告。我认为用户会认为这是一个 bug。
我想编译器可以通过对你提出的 (void)0 发出警告,并且只特殊处理 ((void)0) 来避免这个问题。因此,你最好使用 ((void)0),但我对此表示怀疑。
通常情况下,将某些东西强制转换为空类型(加或不加额外的括号都可以),习惯上意味着“忽略这个”。例如,在 C 代码中,将函数参数强制转换为空类型以抑制未使用变量的警告。因此,在这方面发出警告的编译器也会比较不受欢迎,因为抑制一个警告只会给你带来另一个警告。
请注意,在 C++ 中,标准头文件可以相互包含。因此,如果你使用了任何标准头文件,assert 可能已经被定义过了。因此,你的代码在这个方面是不可移植的。如果你想要做到“普遍适用”,通常应该将任何标准头文件中定义的宏视为保留标识符。你可以取消定义它,但是为你自己的断言使用不同的名称会更明智一些。我知道这只是一个示例,但我不明白为什么你想以“普遍适用”的方式定义 assert,因为所有 C++ 实现都已经有了它,而且它并不做你在这里定义的事情。

12

do { } while(0)怎么样?是的,它会添加代码,但我相信大多数编译器今天都能够对其进行优化。


8

分号被视为标准的无操作符。请注意编译器可能不会从中生成任何代码。


好的,与链接问题的答案表明这并不是一个好主意。 - sharptooth

5

我对这个比较晚知道,但是在我的Arduino项目中需要loop()函数,在其中所有处理都是通过定时器中断服务程序(ISR)完成的。我发现内联汇编代码对我很有用,而且不需要定义一个函数:

void loop(){
  __asm__("nop\n\t");             // Do nothing.
}

1
+1 但是......这个“no-op”的区别与其他答案的“noop”在于,这个实际上需要一个处理器周期来执行,而其他的则是“如果可以优化成无操作,则执行”。并不是说它更好或更差,因为这取决于用例;有些人可能会受到性能差异的影响。 - UKMonkey
谢谢你的评论,我一直在寻找一个好的“cpp版本”来执行nop指令,但我找到了这个问题。幸运的是,你的答案正是我所期待的 :) 不过,这段代码是否具有可移植性呢? - undefined

5

我认为这里的目的,以及不将宏定义为空的原因,是为了要求用户添加一个;。为此,在任何可以使用语句的地方,(void)0(或((void)0),或其他类似的变体)都可以使用。

我发现这个问题是因为我需要在全局作用域中执行相同的操作,而普通语句在这里是非法的。幸运的是,C++11为我们提供了一种替代方法:static_assert(true, "NO OP")。这可以在任何地方使用,并实现了我的目标,在宏后面要求一个;。(在我的情况下,该宏是用于解析源文件的代码生成工具的标记,因此在编译为C++时,它将始终是无操作。)


1
"而且关于:"

#define NOP() ({(void)0;})

或者只是
#define NOP() ({;})

这个是可移植的吗? - user16217248

1
我建议使用:

static_cast<void> (0)   

0
    inline void noop( ) {}

自我记录


1
这个回答如何回答“它是否具有普适性?是否有更好的方法?” - user585968
1
通常避免只有代码的答案。考虑添加一个“描述”来帮助解释您的代码。我认为你可以做得比“自我记录”更好。谢谢。 - user585968
2
这可能会导致函数调用,这绝不是一个“无操作”。 - sharptooth
3
哪个C++编译器在优化时生成这些代码? - gerardw
另一种选择是在调用的地方放置 []{} - Tobias

0

这段代码不会被优化省略

static void nop_func()  {   }
typedef void (*nop_func_t)();
static nop_func_t nop = &nop_func;

for (...)
{
    nop();
}

@sharptooh 是的,通过变量指针调用函数而不是内联。 - iperov
2
优化器是否禁止推断被调用的函数并内联调用? - sharptooth
1
这将被优化掉,我只是看了编译器的输出,无论您是说静态、内联还是什么都不说。但是,您可以通过定义函数 void nop_func(){asm("");} 来阻止所有优化。 - Wolfgang Brehm

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