【问题标题】:Why is (void) 0 a no operation in C and C++?为什么 (void) 0 在 C 和 C++ 中是无操作?
【发布时间】:2011-01-13 00:37:33
【问题描述】:

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

对于__noop,MSDN 表示它是编译器提供的内在函数。来到(void) 0 〜为什么编译器将其解释为无操作?它是 C 语言的一个棘手用法,还是标准明确说明了它?甚至这与编译器实现有关?

【问题讨论】:

  • 通过仅给出0; 作为声明,我不会收到警告或错误,并且我确信它不会执行任何有效操作并且等于无操作;即使是这样,为什么要将其强制转换为 void?另外,如果是#define dbgprintf (void) 0,当它被称为dbgprintf("Hello World!"); -> (void) 0("Hello World!"); - 这是什么意思?
  • 这应该是#define dbgprintf(x) (void)0; 虽然我发现#define dbgprintf(x) 完全足够了。我认为强制转换为 void 是为了删除任何返回值,因此如果在需要值(并且不应该)的上下文中使用它,它将导致错误/警告,而不是静默传递。
  • 是的,我没有注意到 #define 忽略参数 (x) 只是做了一个 (void) 0。感谢您指出:)
  • 好吧,假设你是一个编译器。你会为这个语句生成什么代码:(void) 0;?现在你知道了。
  • 为什么不使用#define dbgprintf 而不是#define dbgprintf ((void) 0) ??? @legends2k 有原因吗?

标签: c++ c compiler-construction language-details


【解决方案1】:

(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

【讨论】:

  • 您应该注意,用作宏(例如,#define noop (void)0)时,(void) 可防止它被意外用作值(如 int x = noop;
  • 感谢 Chris,澄清了为什么将类型转换为 void;它可以防止意外使用上述 noop exp。像赋值这样的表达,现在我明白了。
  • @Chris:把它作为答案:)。
  • @Andrew - 我希望我有,只是我今天已经达到了我的代表上限。我所希望的最好的结果就是一个被接受的答案。
  • @Chris:我现在被“接受”有点不公平 - 但至少你有很多很棒的评论投票,包括我的。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    我认为您说的是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 节):

    标题&lt;assert.h&gt; 定义了assert 宏并引用了另一个宏,

    NDEBUG
    

    不是由&lt;assert.h&gt; 定义的。如果NDEBUG 被定义为源文件中包含&lt;assert.h&gt; 的位置处的宏名称,则assert 宏被简单地定义为

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

    我认为将调试打印宏定义为等于(void)0 没有多大意义。你能告诉我们这是在哪里完成的吗?

    【讨论】:

    • 是的,它是 glibc 而不是 glib,谢谢通知;在问题中更正它。
    • doc.ddart.net/msdn/header/include/assert.h.html - 这是我在(void) 0 中看到的众多 assert.h 之一。
    • @legends2k: assert(expr); 不是调试打印宏。我上面的回答回答了你关于assert()的问题。
    • 我同意,这是断言。但整个疑问是要了解更多关于(void) 0 的信息,因为没有操作,基于语言或编译器结构。谢谢!
    【解决方案4】:

    即使是这样,为什么要将其强制转换为 void?此外,如果#define dbgprintf (void) 0,当它被称为 dbgprintf("Hello World!"); -> (void) 0("Hello World!"); - 这是什么意思? – 传奇2k

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

    无效(0)

    那么替换时不会发生X的重写,所以dbgprintf("Helloworld")不会被转换为(void) 0("Hello world"),而是转换为(void) 0; - 不仅宏名 dbgprint 被 (void) 0 替换,而且整个调用 dbgprintf("...")

    【讨论】:

      【解决方案5】:

      在 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”字符。所以我想一切都清楚了:宏 TRACE[#define TRACE ((void)0)] 的定义根据 c++ 语法是非法的,但是 Visual Studio 的 c++ 编译器支持这种行为作为编译器扩展。所以我的结论是: [#define TRACE ((void)0)] 是非法的 c++ 语句,你最好不要使用它。但是 [#define TRACE(x) ((void)0)] 是法律声明。就是这样。

      【讨论】:

        猜你喜欢
        • 2011-12-20
        • 1970-01-01
        • 2010-11-05
        • 1970-01-01
        • 2012-03-11
        • 2019-12-08
        • 2018-03-09
        • 2023-04-04
        • 1970-01-01
        相关资源
        最近更新 更多