如题,如果没有这个功能,为啥不直接编译后输出信息告诉我们哪些代码是UB,这样不就可以极大降低我们写出UB代码了么?


C++编译器warning一坨一坨的,已经告诉你了。但是还有一部分未定义行为其实是运行时行为,运行时行为很多不能静态分析出来。


clang10有,针对悬垂指针的,编译时就能查出,目前尚在完善中,我之前写了一篇介绍的文章:

日撸代码100行:C++编译器已可识别dangling pointer(悬垂指针)?

zhuanlan.zhihu.com图标

如果是越界、野指针等,需要将程序运行一下,很多工具,如sanitizer,gcc/clang在编译的时候加个选项,然后一运行就能告诉你哪一行代码越界了。santitizer教程:

https://github.com/google/sanitizers/wiki/AddressSanitizer?

github.com


你是不是编译程序都不加-Wall-pedantic的啊……

不过确切来说UB和上面提到的报warning并不是包含关系,所以的确有很多UB行为是漏网之鱼。

评论指出我后面写的是未指定行为,不是未定义行为,所以不用往下看了。


比如求值顺序,cpprefence上就有例子:

求值顺序 - cppreference.com?

zh.cppreference.com

求值任何表达式的任何部分,包括求值函数参数的顺序都是未说明的(除了下列的一些例外)。编译器能以任何顺序求值任何操作数和其他子表达式,并且可以在再次求值同一表达式时选择另一顺序。

C++ 中无从左到右或从右到左求值的概念。这不会与运算符的从左到右及从右到左结合性混淆:表达式 a() + b() + c() 由于 operator+ 的从左到右结合性被分析成 (a() + b()) + c() ,但可在运行时首先或者最后或者 a()b() 之间对 c 函数调用求值:

#include &
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main() {
z(a(), b(), c()); // 允许全部 6 种输出排列
return a() + b() + c(); // 允许全部 6 种输出排列
}

严格来说这里就有UB的存在,但这个UB有没有影响又是另一回事了——你真的需要依赖abc三个函数输出的顺序吗?

如果这种UB都要汇报,那report文件怕是比include展开的源代码还长了。而且这种UB除了用单参数函数式去写以外根本没法规避。

(如果有contract的话,或许可以通过更严格的expects/ensures来促使编译器发现因为UB而与开发者预期不一致的情况?但是到时候怕是1行C++配上100行expects,还不一定能覆盖得全)

对于悬垂指针、内存泄漏这种,我觉得不叫UB,这种叫bug……

如果想让编译器输出所有bug……这应该算是翻版停机问题了吧……


是的,有的。

gcc

https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/?

developers.redhat.com

clang

http://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html?

clang.llvm.org


Clang 11 documentation?

clang.llvm.org


理论上来说是可以的,实际上很多警告内容都是在反映UB,所以建议多关注一下警告


《C专家编程》里写到过这个问题

当时在C/C++早期,就有人提议把lint(检查程序)融合在编译器里,理由就是能够减少不安全的代码.但是被那些编写编译器的人否决了,认为功能应该最简.

现在的C/C++编译器已经初步具备了lint程序的部分功能(Warning部分),但是受以前的影响,lint程序的功能现在仍然没有完全并入编译器里.所以如果现在想要写出更加安全的代码,可以手动对代码使用cppcheck或pc-lint,并在编译参数里加上-Wall.


只用过clang和gcc,他们都是有warning的,而warning里有很多是可能引起ub的代码,比如数组越界,还有比如x++ + ++x这种坑爹玩意儿

但ub太多了不可能全部查到的吧……


推荐阅读:
相关文章