C++:汇编代码包含断言结果
C++: assembly code contains assert results
代码如下:
#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()有重叠..
看起来你正在使用 GCC 和 GNU C 库,所以假设 NDEBUG
没有定义,assert
宏可能定义如下:
# 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"
,因为它不知道 Collatz 猜想是否为真(公平地说,目前没有数学家这样做)。
* 我通过 运行 g++ -E assert.cpp -o assert.ii
获取预处理代码,然后 运行 cat assert.ii | sed '/^#/d'
删除文件名和行号的标记并重新格式化将代码转换为更易读的内容,然后手动将 __PRETTY_FUNCTION__
替换为 "int main()"
。如果您对编译器为什么要执行某些操作感到困惑,那么查看这样的预处理输出可能会有所帮助。
代码如下:
#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()有重叠..
看起来你正在使用 GCC 和 GNU C 库,所以假设 NDEBUG
没有定义,assert
宏可能定义如下:
# 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"
,因为它不知道 Collatz 猜想是否为真(公平地说,目前没有数学家这样做)。
* 我通过 运行 g++ -E assert.cpp -o assert.ii
获取预处理代码,然后 运行 cat assert.ii | sed '/^#/d'
删除文件名和行号的标记并重新格式化将代码转换为更易读的内容,然后手动将 __PRETTY_FUNCTION__
替换为 "int main()"
。如果您对编译器为什么要执行某些操作感到困惑,那么查看这样的预处理输出可能会有所帮助。