为什么 (void) 0 在 C 和 C++ 中是无操作?

问题描述 投票:0回答:6

我在glibc中看到了调试printfs,如果定义了

NDEBUG
,它的内部定义为(void) 0。同样,Visual C++ 编译器的
__noop
也在那里。前者适用于 GCC 和 VC++ 编译器,而后者仅适用于 VC++。现在我们都知道,上述两条语句都会被视为无操作,不会生成相应的代码;但这就是我有疑问的地方。

对于

__noop
,MSDN 说它是编译器提供的内部函数。来到
(void) 0
〜为什么它被编译器解释为no op?这是 C 语言的一种棘手用法还是标准对此有明确说明?或者甚至这与编译器实现有关?

c++ c compiler-construction
6个回答
82
投票

(void)0
(+
;
) 是一个有效的,但“什么也不做”的 C++ 表达式,仅此而已。它不会转换为目标体系结构的
no-op
指令,只要语言需要完整的语句(例如作为跳转标签的目标,或在
if
子句的主体中),它只是一个作为占位符的空语句).

来自 Chris Lutz 的评论

应该注意的是,当用作宏时(例如,

#define noop ((void)0)
),
(void)
可以防止它被意外用作值(如
int x = noop;
)。

对于上面的表达式,编译器会正确地将其标记为无效操作。 GCC 吐口水

error: void value not ignored as it ought to be
,VC++ 咆哮
'void' illegal with all types


12
投票

任何没有任何副作用的表达式都可以被编译器视为无操作,编译器不必为其生成任何代码(尽管可能会生成)。碰巧,编译器(和人类)很容易将转换然后不使用转换结果视为没有副作用。


4
投票

我认为你正在谈论glibc,而不是glib,并且所讨论的宏是

assert
宏:

在glibc的

<assert.h>
中,定义了
NDEBUG
(无调试),
assert
定义为:

#ifdef NDEBUG
#if defined __cplusplus && __GNUC_PREREQ (2,95)
# define __ASSERT_VOID_CAST static_cast<void>
#else
# define __ASSERT_VOID_CAST (void)
#endif
# define assert(expr)           (__ASSERT_VOID_CAST (0))
#else
/* more code */
#endif

基本上意味着

assert(whatever);
相当于
((void)(0));
,并且什么也不做。

来自 C89 标准(第 4.2 节):

标头

<assert.h>
定义了
assert
宏并引用另一个宏,

NDEBUG

不是由

<assert.h>
定义的。 如果在源文件中包含
NDEBUG
的位置将
<assert.h>
定义为宏名称,则
assert
宏简单地定义为

#define assert(ignore) ((void)0)

我认为定义调试打印宏等于

(void)0
没有多大意义。 你能告诉我们这是在哪里完成的吗?


1
投票

即使是这样,为什么 type 将其强制转换为 void?另外,如果是 #define dbgprintf (void) 0,当它被称为

dbgprintf("Hello World!"); -> (void) 0("Hello World!");
时 - 这意味着什么?

宏用其他东西替换你的代码,所以如果你#define dbgprint(接受x)为

void (0)

那么替换时不会发生 X 的重写,因此

dbgprintf("Helloworld")
不会被转换为
(void) 0("Hello world")
,而是转换为
(void) 0;
- 不仅宏名称 dbgprint 被替换为
(void) 0
,而且整个调用
dbgprintf("...")


0
投票

在 Windows 上,我在 Main.cpp 中尝试一些类似的代码:

#include <iostream>
#define TRACE ((void)0)
int main() {
  TRACE("joke");
  std::cout << "ok" << std::endl;
  return 0;
}

然后,我使用 Main.i 输出构建发布版本 exe 文件。在Main.i文件中,TRACE宏被替换为:

((void)0)("joke")
,Visual Studio给出警告:“警告C4353:使用非标准扩展:常量0作为函数表达式。

使用“__noop”内部函数代替”。

运行exe文件,控制台打印出“ok”字符。

所以我想一切都清楚了:根据C++语法,宏TRACE[#define TRACE ((void)0)]的定义是非法的,但是Visual Studio的C++编译器作为编译器扩展支持这种行为。

所以我的结论是: [#define TRACE ((void)0)] 是非法的 c++ 语句,你最好不要使用它。但 [#define TRACE(x) ((void)0)] 是合法的语句。仅此而已。


0
投票

这是最近 GCC 抱怨的警告

-Wall -Wextra
打开的情况,即使出现误报,您也应该这样做:

  1. #define dbgprintf(x)

    如果语句不带大括号,如
    if (cond) dbgprintf(x)
    会导致
    “警告:建议在‘if’语句中用大括号括住空主体 [-Wempty-body]”
  2. #define dbgprintf(x) 0

    “警告:语句无效 [-Wunused-value]”(这就是我们的意图)
  3. #define dbgprintf(x) (void)0

    将强制转换为
    void
    会使 GCC 抑制警告。

顺便说一句,同样的

-Wall -Wextra
不会抱怨这些。

© www.soinside.com 2019 - 2024. All rights reserved.