在 C 中实现无操作语句的可移植方法是什么?
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
时发出。
它是普遍便携的吗?有更好的办法吗?
One in a while there's a need for a no-op statement in C++. For example when implementing assert()
which is disabled in non-debug configuration (also see this question):
#ifdef _DEBUG
#define assert(x) if( !x ) { \
ThrowExcepion(__FILE__, __LINE__);\
} else {\
//noop here \
}
#else
#define assert(x) //noop here
#endif
So far I'm under impression that the right way is to use (void)0;
for a no-op:
(void)0;
however I suspect that it might trigger warnings on some compilers - something like C4555: expression has no effect; expected expression with side-effect
Visual C++ warning that is not emitted for this particular case but is emitted when there's no cast to void
.
Is it universally portable? Is there a better way?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
最简单的无操作就是根本没有代码:
然后用户代码将具有:
您提到的替代方案也是无操作:
(void)0;
,但是如果您要在宏内部使用它,您应该将;
保留在一边,以便调用者添加:(如果
;
是宏的一部分,那么将会有一个额外的;
那里)The simplest no-op is just having no code at all:
Then user code will have:
The alternative that you mention is also a no-op:
(void)0;
, but if you are going to use that inside a macro, you should leave the;
aside for the caller to add:(If
;
was part of the macro, then there would be an extra;
there)不太可能,因为
((void)0)
是标准assert
宏在NDEBUG
时扩展为的内容被定义。因此,只要编译包含断言的代码以供发布,任何发出警告的编译器都会发出警告。我希望用户会认为这是一个错误。我认为编译器可以通过对您的提案
(void)0
发出警告,同时仅特殊对待((void)0)
来避免该问题。所以你最好使用((void)0)
,但我对此表示怀疑。一般来说,将某些内容强制转换为 void,无论是否带有额外的封闭括号,惯用的意思是“忽略此”。例如,在 C 代码中,将函数参数强制转换为
void
以抑制未使用变量的警告。因此,就这一点而言,发出警告的编译器将相当不受欢迎,因为抑制一个警告只会给您带来另一个警告。请注意,在 C++ 中,标准头允许相互包含。因此,如果您使用任何标准标头,
assert
可能已由该标准标头定义。所以你的代码在该帐户上是不可移植的。如果您说的是“通用可移植”,则通常应该将任何标准标头中定义的任何宏视为保留标识符。您可以取消定义它,但是为您自己的断言使用不同的名称会更明智。我知道这只是一个例子,但我不明白为什么你想要以“通用可移植”的方式定义assert
,因为所有 C++ 实现都已经有了它,但它没有做你在这里定义它要做的事情。Unlikely, since
((void)0)
is what the standardassert
macro expands to whenNDEBUG
is defined. So any compiler that issues warnings for it will issue warnings whenever code that contains asserts is compiled for release. I expect that would be considered a bug by the users.I suppose a compiler could avoid that problem by warning for your proposal
(void)0
while treating only((void)0)
specially. So you might be better off using((void)0)
, but I doubt it.In general, casting something to void, with or without the extra enclosing parens, idiomatically means "ignore this". For example in C code that casts function parameters to
void
in order to suppress warnings for unused variables. So on that score too, a compiler that warned would be rather unpopular, since suppressing one warning would just give you another one.Note that in C++, standard headers are permitted to include each other. Therefore, if you are using any standard header,
assert
might have been defined by that. So your code is non-portable on that account. If you're talking "universally portable", you normally should treat any macro defined in any standard header as a reserved identifier. You could undefine it, but using a different name for your own assertions would be more sensible. I know it's only an example, but I don't see why you'd ever want to defineassert
in a "universally portable" way, since all C++ implementations already have it, and it doesn't do what you're defining it to do here.do { } while(0)
怎么样?是的,它添加了代码,但我确信当今大多数编译器都能够对其进行优化。How about
do { } while(0)
? Yes it adds code, but I'm sure most compilers today are capable of optimizing it away.;被视为标准无操作。请注意,编译器可能不会从中生成任何代码。
; is considered as standard no-op. Note that it is possible that the compiler will not generate any code from it.
我在这方面已经很晚了,但我需要在 Arduino 项目中使用相同的循环(),其中所有处理都是在定时器中断服务例程(ISR)中完成的。发现内联汇编代码对我有用,无需定义函数:
I'm rather late to the party on this one but I needed the same for a loop() in an Arduino project where all processing is done in timer interrupt service routines (ISR). Found the inline assembler code worked for me without defining a function:
我认为这里的目标,以及不将宏定义为空的原因,是要求用户添加
;
。为此,只要语句合法,(void)0
(或((void)0)
,或其他变体)都是美好的。我发现这个问题是因为我需要在全局范围内做同样的事情,其中普通的旧语句是非法的。幸运的是,C++11 为我们提供了一个替代方案:
static_assert(true, "NO OP")
。这可以在任何地方使用,并实现我在宏后面需要一个;
的目标。 (在我的例子中,宏是解析源文件的代码生成工具的标签,因此当将代码编译为 C++ 时,它将始终为 NO-OP。)I think the objective here, and the reason not to define the macro to nothing, is to require the user to add a
;
. For that purpose, anywhere a statement is legal,(void)0
(or((void)0)
, or other variations thereupon) is fine.I found this question because I needed to do the same thing at global scope, where a plain old statement is illegal. Fortunately, C++11 gives us an alternative:
static_assert(true, "NO OP")
. This can be used anywhere, and accomplishes my objective of requiring a;
after the macro. (In my case, the macro is a tag for a code generation tool that parses the source file, so when compiling the code as C++, it will always be a NO-OP.)请注意 noop 的含义,因为它可以有两种解释:一种实际上什么都不做,另一种是执行汇编指令 noop(实际上浪费一个 CPU 周期)。
这种差异很重要。
while(true){}
可能是无限循环,也可能被完全优化,因为循环实际上什么都不做。while(true){__asm__("nop\n\t"); } 总是产生无限循环。
您可以通过使用 gcc14.2 和编译器优化选项进行编译,在 https://godbolt.org/ 中测试这些示例
-O0
与-O3
Be careful about what you mean by noop, as it can be interpreted two ways: one is actually doing nothing, and the other is executing the assembly instruction noop (actually wasting one CPU cycle).
This difference is important.
while(true){}
may be an infinite loop or may be optimized out completely as the loop actually does nothing.while(true){__asm__("nop\n\t"); }
always produces an infinite loop.You can test these example in https://godbolt.org/ by compiling with gcc14.2, with compiler optimization options
-O0
v.s.-O3
我建议使用:
I recommend using:
那么:
或者只是
And what about:
or just
AFAIK,它是普遍便携的。
也会做的。
另一个选择可能是这样的:
但是,我会坚持使用
(void)0
或使用像 __noopAFAIK, it is universally portable.
will do as well.
Another option may be something like this:
However, I'd stick to
(void)0
or use intrinsics like __noop自我记录
Self-documenting
这段代码不会被优化省略
this code will not omitted by optimization
方法有很多,下面是我在 MSVS2019 cpp 编译器中对其中一些方法进行的比较。
在反汇编中转换为
nop
操作,需要 1 个机器周期。do {} while (0);
生成一些指令,从而生成更多周期。简单的
;
不会生成任何内容。There are many ways, and here is the comparison I've made to some of them in MSVS2019 cpp compiler.
Transaltes to
nop
operation in disassembly which takes 1 machine cycle.do {} while (0);
generates some instructions which generates some more cycles.Simple
;
generates nothing.