如題,如果沒有這個功能,為啥不直接編譯後輸出信息告訴我們哪些代碼是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太多了不可能全部查到的吧……


推薦閱讀:
相关文章