若是你没兴趣/没时间看具体解释、只想快速排错,请明确:这里列出了我的认为应当看成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?