各大C++編譯器可以編譯輸出UB提示信息嗎?
如題,如果沒有這個功能,為啥不直接編譯後輸出信息告訴我們哪些代碼是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.comclang
http://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html?clang.llvm.orgClang 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太多了不可能全部查到的吧……
推薦閱讀: