【问题标题】:I'd like to understand this assert macro我想了解这个断言宏
【发布时间】:2020-12-25 11:50:11
【问题描述】:

在 Visual Studio 中,断言宏的定义如下:

#define assert(expression) (void)(                                                       \
            (!!(expression)) ||                                                              \
            (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
        )

我在 || 之后得到了正确的部分仅当左表达式为 false 时才计算,并引发异常。

我有 3 个问题:

  1. !!双重否定是将'表达式'类型转换为布尔值,对吗?如果他们被排除在外,会发生什么?当 ' 表达式与另一个表达式进行逻辑或运算时,它会被隐式转换为布尔值,对吗?在:

    浮动() || /* 调用抛出的函数*/;

float() 还是转换成 bool 类型?

2.在||之后我们有运算符:

 ( /* call function that throws */  , 0 )

逗号是一个序列点,所以首先调用函数,然后这个表达式被评估为 0。如果函数调用没有终止程序,那么表达式就是:

!!(expression)) || 0

逗号后面的 0 有什么意义?如果 if 不存在,则表达式为:

!!(expression)) || / * call function that throws */

哪个是一样的,对吧?

  1. 最后是 (void),我想知道这是干什么用的。

【问题讨论】:

  • 如果我有一个类型(愚蠢地?让我们怀疑并假设有充分的理由)覆盖 operator|| 怎么办?嗯,可能((bool)(expression) || ... 会更安全。他们也可能已经覆盖了operator!
  • 需要逗号运算符,因为_wassert 返回void。而_wassert 没有throw,它要么以退出代码 3 中止,进入调试器,要么忽略问题并继续。可以使用_set_error_mode 更改其行为。该宏还使用带有if 语句的宏来避免悬空 else 问题,并且有点聪明,因为它将其保留为 表达式 而不是 语句
  • 最顶层的(void) 是为未使用的表达式结果压制编译器警告,并防止意外使用表达式结果。而且宏可能和编译为C或C++或C++/CLI时使用的宏是一样的,所以必须满足这些语言的交集。
  • @Eljay,很酷,现在让它成为答案 :)

标签: c++ macros


【解决方案1】:

让我们分解代码:

#define assert(expression) (void)(                                                       \
            (!!(expression)) ||                                                              \
            (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \
        )

(void) 是为了压制编译器警告表达式结果未使用。并且还确保断言不会被意外地用作更大表达式的一部分。

!!(expression)!! 部分是为了确保表达式的计算结果为falsetrue

但是恶作剧的 C++ 代码可能会覆盖 operator! 并在这里造成一些混乱。

为什么是 C 风格的演员,而不是 static_cast<bool>?我怀疑相同的头文件用于支持 C 和 C++,也可能是 C++/CLI。 (如果是这样,可能有理由在两种语言特定的宏中使用 static_cast<bool> 用于 C++ 和 (bool) 用于 C。)

如果expression 对象有一个operator bool 转换,它将被!! 使用。 (但也会被(bool)(expression) 使用。)

无论如何,!! 是不必要的观察对我来说似乎是正确的。 C++ 中 truthy(非零)或 falsy(零)的表达式是该语言的一部分。

使用(x(), 0) 逗号运算符是因为x(在本例中为_wassert)返回一个空值。如果b 是无效的,则不能执行a || b,因此诡计只是使其调用无效,并且表达式导致int。无所谓是0

_wassert 不会抛出。它可能会以退出代码 3 中止。它可能会闯入调试器。

它可以被忽略并继续。并且断言被违反可能会导致轴损坏,或者代码可能有一些防御性编程来处理断言违反。

由于 assert 类函数宏被编写为 表达式,它巧妙地避免了“悬空if 宏”的问题(使用了if (!(expression)) ... 构造),并且是表达式而不是语句

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-02
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-08
    • 1970-01-01
    相关资源
    最近更新 更多