若是你沒興趣/沒時間看具體解釋、只想快速排錯,請明確:這裏列出了我的認爲應當看成error但被C編譯器(少許狀況是C++編譯器)默認設定爲warning的編譯選項(CFLAGS/CXXFLAGS),比「忽略全部warning」要更安全,比開啓「視全部warning爲error」要寬鬆精準。支持包括主流的Visual Studio和GCC這兩個編譯器。程序員
if (CMAKE_SYSTEM_NAME MATCHES "Windows") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431 /we4133 /we4716 /we6244 /we6246 /we4457 /we4456 /we4172 /we4700 /we4477 /we4018 /we4047") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} /we4013 /we4431 /we4133 /we4716 /we6244 /we6246 /we4457 /we4456 /we4172 /we4700 /we4477 /we4018 /we4047") elseif (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "Darwin") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=return-type -Werror=shadow -Werror=return-local-addr -Werror=uninitialized -Werror=format -Werror=sign-compare -Werror=int-conversion") set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=return-type -Werror=shadow -Werror=return-local-addr -Werror=uninitialized -Werror=format -Werror=sign-compare -Werror=int-conversion") endif()
項目屬性->配置屬性->C/C++->高級->將特定的警告視爲錯誤,填入相應的警告、錯誤代號:windows
4013;4431;4133;4716;6244;6246;4457;4456;4172;4700;4477;4018;4047;4013;4431;4133;4716;6244;6246;4457;4456;4172;4700;4477;4018;4047安全
CFLAGS += -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=return-type -Werror=shadow -Werror=return-local-addr -Werror=uninitialized -Werror=format -Werror=sign-compare -Werror=int-conversion
gcc xxx.c -Werror=implicit-function-declaration -Werror=implicit-int -Werror=incompatible-pointer-types -Werror=return-type -Werror=shadow -Werror=return-local-addr -Werror=uninitialized -Werror=format -Werror=sign-compare -Werror=int-conversion
說說爲何要定製上面一大串CFLAGS/CXXFLAGS:默認的CFLAGS/CXXFLAGS過度相信程序員,而小白則沒法駕馭。問題比較嚴重的是純C的代碼,C++稍微好一些,所以這裏主要說C特有的,剩餘少許的是C/C++共有的問題。bash
不少程序小白(甚至工做多年的老鳥)認爲:函數
C代碼報error須要消滅掉,報warning沒啥事兒的趕忙提交版本/給QA測試/上線,PM或老闆等着呢/別浪費我沒必要要時間/warning都是雞毛蒜皮問題...佈局
遺憾的是這種想法並不罕見,彷佛以爲「不crash就沒問題」的心態,一旦出問題查起來極可能手忙腳亂,由於crash/bug極可能很差重現(血淚教訓:移植ncnn爲純C代碼,忘記include相應頭文件,手機上運行出現難復現的crash)visual-studio
.c文件被C編譯(而不是C++編譯器)編譯。最多見的case是(純C代碼,C++沒有這個問題):沒有找到函數聲明的狀況下調用函數。也就是,沒有實現函數xx()
,或者實現了函數可是沒有#include
頭文件,而後調用xx()
。細分下來又有這幾種狀況:測試
xx()
不是編譯器內置函數;編譯階段僅僅報warning,運行時結果不對/不穩定xx()
不是編譯器內置函數;連接階段報錯說找不到符號(函數定義)xx()
是編譯器內置函數;編譯階段僅僅報warning,運行時結果正確xx()
是編譯器內置函數;編譯階段報warning;運行時結果正確上述四種狀況咱們一一舉例說明。每一個例子都基於CMake構建。優化
CMakeLists.txt
cmake_minimum_required(VERSION 3.14) project(hoho) add_library(hoholib src/hoholib.c) add_executable(hohoexe src/hohoexe.c) target_link_libraries(hohoexe hoholib)
hoholib.c
void hello() { const char* name = "Chris"; print_hello(name); }
hohoexe.c
#include <stdio.h> int main() { hello(); return 0; }
VS2017編譯輸出:
GCC編譯輸出:
能夠看到,問題在連接階段纔會報error,編譯階段僅報warning。編庫是不須要連接的,只須要編譯。若是忽略編庫階段的上述warning那就是埋雷。
首先明確下什麼是編譯器內置函數:對於gcc而言,定義了printf、fabs等函數,而這些函數是在C標準庫、math庫中定義的,gcc爲了優化而提供了本身的實現,而若是用戶沒有連接相應的庫、沒有包含相應的頭文件,則連接階段找不到對應的符號表,但能找到built-in函數,於是直接調用built-in函數。這就是爲何「把(1)中調用的未定義函數換成fabs、printf等函數,gcc下連接階段也不會報錯反而能正確輸出結果」的緣由。參考:關於gcc內置函數和c隱式函數聲明的認識以及一些推測
遺憾的是,這種取巧的作法對於Visual Studio行不通,由於cl.exe並無和gcc徹底相同的編譯器內置函數。cl.exe的編譯器內置函數叫作Compiler Intrinsics,並無定義printf、fabs等函數。這就解釋了「爲何調用了printf、fabs等gcc內置同名函數的代碼,gcc下連接正常運行正確但在VS下連接出錯」。
仍是上面的CMake配置,C代碼爲:
hoholib.c
void hello() { const char* name = "Chris"; printf("hello, %s\n", name); }
hohoexe.c
#include <stdio.h> int main() { hello(); return 0; }
VS下編譯報錯,gcc下則編譯連接都無error,能夠運行並獲得預期結果。
這種狀況下,gcc編譯連接無error且結果正確,VS則可能編譯就報錯,也可能編譯連接經過但結果不對。
cmake_minimum_required(VERSION 3.14) project(hoho) add_executable(hohoexe src/hohoexe.c)
若是hohoexe.c的代碼是這樣:
int main() { const char* name = "Chris"; printf("hello, %s\n", name); return 0; }
則,VS下編譯報錯,gcc下編譯連接無error且結果符合預期。
若是hohoexe.c的代碼是這樣:
#include <stdio.h> int main() { double x = -3.3; double y = fabs(x); printf("fabs(%lf)=%lf\n", x, y); return 0; }
則VS下編譯連接無error但結果不對:
fabs(-3.300000)=-858993460.000000
這種狀況下,VS和gcc都直接編譯報錯,沒什麼好說的:
#include <stdio.h> int main() { const char* name = "Chris"; print_hello(name); return 0; }
簡單總結一下上述(1)~(4):對於printf、fabs、sin等常見函數,gcc有內置函數的實現使得一些代碼儘管報warning但也能運行;一樣的代碼在Visual Studio下無法編譯連接;對於用戶自定義的函數,若是是編庫,則編譯階段只報warning不報error,若是是可執行程序則會報error。對於小白和老菜鳥們,應該不管如何都把「未聲明函數就使用」強制做爲error,絕對不虧。C編譯器的這個現象難免讓人疑惑:你這該報錯的不報錯,誤導人啊!然而有種說法是爲了兼容老版本代碼。嗯,簡直無語的C編譯器默認編譯選項!
被C編譯器默認報爲warning而不是error、但實際上又很重要的編譯選項,還有不少,而其中不少編譯選項在C++中是默認爲error的。若是項目容許,不妨使用C++編譯器。而對於必須使用純C的項目,就須要把C編譯器中的這些嚴重warning都設定爲error,提早發現問題解決問題。
下列警告應當視做錯誤(血淚教訓):
VS下爲/we4013
。gcc下用-Werror=implicit-function-declaration
VS下開關爲/we4431
。gcc下用-Werror=implicit-int
。注:其實implicit-function-declaration和implicit-int能夠用一個implicit來替代。
VS下爲/we4133
。gcc下用-Werror=incompatible-pointer-types
VS下爲/we4716
。gcc下用-Werror=return-type
內層做用域從新聲明/定義了與外層做用域中同名的變量。
VS下有好幾個開關:/we6244 /we6246 /we4457 /we4456
(MSDN上還有個 /we2082但實際用的時候提示無效: 命令行 warning D9014: 值「2082」對於「/we」無效;假定爲「5999」)。gcc下用-Werror=shadow
VS下的開關:/we4172
。gcc下用-Werror=shadow -Werror=return-local-addr
。
函數調用完畢,沒法保證用過的棧幀空間後續被如何使用(編譯器是否開啓優化、棧幀佈局結構都有影響),不可僥倖。
VS下的開關:/we4700
。gcc下用-Werror=uninitialized
。
例如%d匹配到了double,結果確定不對,應當提早檢查出來。
VS下的開關:/we4477
。gcc下用-Werror=format
。
有符號數可能在比較以前被轉換爲無符號數而致使結果錯誤。
VS下的開關:/we4018
。gcc下用-Werror=sign-compare
。
雖然說能夠把指針的值(一個地址)當作一個int(實際上是unsigned int)來理解,但考慮這種狀況:int a=*p被寫成int a=p而引起錯誤。
VS下的開關:/we4047
。gcc下用-Werror=int-conversion
。
由於上述N條規則是我自行制定的,有些是C++下默認視爲錯誤,有些則是C++下也爲警告。所以不妨把CFLAGS和CXXFLAGS都添加這些檢查規則。
建議基於CMakeLists.txt,現有Visual Studio工程也可配置,具體見文章第一部分「快速配置」。
其餘配置方式說明:
#pragma warning (error: xxxx)
。缺點:只有visual studio工程能用;不能確保全部文件有效gcc gcc xxx.c -Werror=implicit-function-declaration
CMakeLists.txt中配置說明:set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /weXXXX")
(windows)或set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=xxxx")
。
其中,windows格式中XXXX爲警告編號;gcc下xxxx爲警告對應的字符串。這種方式我的推薦。
C++編譯器默認連接C++標準庫,C++標準庫包含了math庫;C編譯器默認連接C標準庫,C標準庫不包含math庫(參考:Why do you have to link the math library in C?)。問題來了:對於gcc,若是純C代碼調用了math函數而沒有設定連接選項-lm
,會使用gcc的built-in函數;一樣的代碼,VS2017並無內置math庫的函數,沒有連接數學庫的秦廣下,爲何也能正確運行?
#include <stdio.h> #include <math.h> int main() { double x = -3.3; double y = fabs(3.3); printf("fabs(%lf)=%lf\n", x, y); return 0; }
/w, /W0, /W1, /W2, /W3, /W4, /w1, /w2, /w3, /w4, /Wall, /wd, /we, /wo, /Wv, /WX (Warning Level)
How to set compiler options with CMake in Visual Studio 2017
Make one gcc warning an error?
Can I treat a specific warning as an error?