C ++:汇编代码包含断言结果

时间:2017-08-08 16:18:57

标签: c++ assembly assert compiler-optimization

以下是代码:

#include <cassert>

int main() {
    assert(true==false); // A
    assert(true==true);  // B
}

这是汇编代码(link):

.LC0:
.string "/tmp/compiler-explorer-compiler11778-61-1sgmkbd.5d1m6g8pvi/example.cpp"
.LC1:
  .string "true==false"
main:
  push rbp
  mov rbp, rsp
  mov ecx, OFFSET FLAT:main::__PRETTY_FUNCTION__
  mov edx, 5
  mov esi, OFFSET FLAT:.LC0
  mov edi, OFFSET FLAT:.LC1
  call __assert_fail
main::__PRETTY_FUNCTION__:
  .string "int main()"

A,它应该触发断言失败,反映在汇编代码中,但B不是。

我的问题是:现在宏assert()用于运行时断言,编译器如何知道结果并将其写入程序集?

编译器:gcc 7.1,优化-O0(即没有优化)。我认为这是因为优化,所以我故意通过这个选项将其关闭(是吗?)。

编辑:现在可以在编译时评估assert(),它似乎与static_assert()重叠..

1 个答案:

答案 0 :(得分:4)

看起来你正在使用GCC和GNU C库,所以假设没有定义NDEBUGassert宏可能定义如下:

# define assert(expr)                                                   \
  ((expr)                                                               \
   ? __ASSERT_VOID_CAST (0)                                             \
   : __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))

(从我的/usr/include/assert.h复制)

替换它(以及它所依赖的所有宏)*会得到类似

的东西
int main() {

  ((true == false)
       ? static_cast<void>(0)
       : __assert_fail("true==false", "assert.cpp", 4, "int main()"));

  ((true == true)
       ? static_cast<void>(0)
       : __assert_fail("true==true", "assert.cpp", 5, "int main()"));
}

即使在-O0,编译器也足够聪明,可以看到true == true而不是true == false,因此它知道它将调用第一个__assert_fail,但它永远不会调用第二。因为它永远不会调用第二个函数,所以它不需要字符串"true == true",也不需要包含它。

如果你做了更难的条件,它就不会知道它需要哪些,所以它会包含所有内容。

编辑添加:即使您添加了另一个条件更复杂的行,也不一定要添加。例如,here我已修改您的代码以添加必须在运行时发生的检查:

#include <cassert>

bool check_collatz_conjecture();

int main() {
    // assert(check_collatz_conjecture());
    assert(true==false);
    assert(true==true);
    assert(check_collatz_conjecture());
}

因为编译器知道第一个断言会被命中,并且因为__assert_fail是用__attribute__((__noreturn__))声明的,所以编译器知道它不需要担心函数的其余部分而且它不会不包含"check_collatz_conjecture()"字符串。如果你取消注释第一个断言,它将同时包括那个和"true == false",因为它不知道Collat​​z猜想是否为真(这是公平的,目前没有数学家)。

*我通过运行g++ -E assert.cpp -o assert.ii获取预处理代码,然后运行cat assert.ii | sed '/^#/d'以删除文件名和行号的标记并将代码重新格式化为更易读的内容,然后手动执行此操作用__PRETTY_FUNCTION__替换"int main()" s。如果你对编译器为什么要做某事感到困惑,那么查看这样的预处理输出可能会有所帮助。