这是一个玩具示例,我认为会调用未定义的行为:
#include <cstdint>
#include <iostream>
#include <vector>
int
main()
{
std::vector<uint16_t> foo = {0, 0x42F6};
std::cout << *reinterpret_cast<float*>(foo.data()) << std::endl;
return 0;
}
我很确定取消引用 reinterpret_cast
的结果会违反严格的别名规则 . 然而:
$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609
$ g++ -fstrict-aliasing -Wstrict-aliasing -fsanitize=undefined -std=c++14 -o a a.cpp
$ ./a
123
没有来自编译器或UB消毒剂的警告 . 为什么不?
3 回答
这并不意味着您没有未定义的行为 . 编译器和清理程序可以尽力检测它,但不保证能够捕获它的每一次出现 .
您可以确定的唯一方法是阅读标准并检查您对
reinterpret_cast
的使用是否定义明确 .你的前提是错的 . 行为未定义 .
编译器不需要警告UB . 有时确实如此,当星星对齐时,但编译器证明UB的存在通常是非常昂贵的 . 事实上,如果有可能,那么语言规则可能会指定程序形成错误 .
UB消毒剂并不完美 . 它无法检测到所有UB . 考虑提交实现此案例检测的功能请求 - 假设尚未请求 .
标准不要求实现来定义某些构造的行为这一事实并不意味着旨在适用于各种目的的实现不应该定义它 . 许多实现都有各种选项,这些选项将导致编译器始终如一地处理某些此类构造,这些构造的定义方式有时甚至是程序员编写某种代码所必需的(即使标准不需要它) .
关于您的示例,有一些实现,其中使用适当的选项组合的调用将产生与您所看到的一样精确定义的行为 . 在许多实现中,所需的选项将包括
-fno-strict-aliasing
,但有些可能还需要其他选项 . 但是,某些实现,尤其是vector<uin16_t>
可能不是32位对齐的那些实现,以及如果不是32位对齐而加载float
将失败的实现,可能不支持定义行为的任何选项组合 .然而,稍微混淆这个问题的事实是,即使不使用(或不支持)这些选项,许多实现也会以一种方式对待这些结构,这种结构通常 - 尽管不可靠 - 与它们的方式一致如果指定和支持这些选项 . 这可能使得难以确定一段代码是否可以以不可预测的方式一致地工作,或者有时可能工作并且有时会失败 .