我经常听到有人说,在编译C和c++程序时,我应该“总是启用编译器警告”。为什么这是必要的?我怎么做呢?

有时我也听到我应该“把警告当作错误”。我应该吗?我怎么做呢?


当前回答

为什么要启用警告?

众所周知,C和c++编译器在默认情况下不擅长报告一些常见的程序员错误,例如:

忘记初始化变量 忘记从函数返回值 printf和scanf族中的参数与格式字符串不匹配 函数的使用没有事先声明(仅限C语言)

这些可以被检测和报告,只是通常不是默认情况;此特性必须通过编译器选项显式地请求。

如何启用警告?

这取决于你的编译器。

微软C和c++编译器理解/W1、/W2、/W3、/W4和/Wall这样的开关。至少使用/W3。/W4和/Wall可能会对系统头文件发出错误的警告,但如果您的项目使用这些选项之一进行了干净的编译,那么就使用它。这些选择是相互排斥的。

大多数其他编译器都理解-Wall、-Wpedantic和-Wextra这样的选项。-Wall是必要的,其余的都是推荐的(注意,尽管它的名字,-Wall只启用最重要的警告,而不是全部)。这些选项可以单独使用,也可以一起使用。

您的IDE可能有办法从用户界面启用这些功能。

为什么我要把警告当作错误?它们只是警告!

A compiler warning signals a potentially serious problem in your code. The problems listed above are almost always fatal; others may or may not be, but you want compilation to fail even if it turns out to be a false alarm. Investigate each warning, find the root cause, and fix it. In the case of a false alarm, work around it — that is, use a different language feature or construct so that the warning is no longer triggered. If this proves to be very hard, disable that particular warning on a case by case basis.

你不希望只是把警告作为警告,即使它们都是假警报。对于发出的警告总数小于7的非常小的项目来说,这是可行的。再多一点,新的警告就很容易淹没在大量熟悉的旧警告中。不要允许这种情况发生。只是让你所有的项目编译干净。

Note this applies to program development. If you are releasing your project to the world in the source form, then it might be a good idea not to supply -Werror or equivalent in your released build script. People might try to build your project with a different version of the compiler, or with a different compiler altogether, which may have a different set of warnings enabled. You may want their build to succeed. It is still a good idea to keep the warnings enabled, so that people who see warning messages could send you bug reports or patches.

如何将警告视为错误?

这同样是通过编译器开关完成的。/WX是微软的,大多数其他人使用-Werror。在这两种情况下,如果产生任何警告,编译都会失败。

这就够了吗?

可能不是!随着优化级别的提高,编译器开始越来越仔细地检查代码,而这种仔细检查可能会发现更多的错误。因此,不要满足于警告开关本身,总是使用它们时,编译优化启用(-O2或-O3,或/O2如果使用MSVC)。

其他回答

一些警告可能意味着代码中可能出现语义错误或可能出现UB。例如;if()之后,一个未使用的变量,一个被局部变量掩盖的全局变量,或者有符号和无符号的比较。许多警告与编译器中的静态代码分析器或在编译时检测到的违反ISO标准有关,这“需要诊断”。虽然在特定情况下,这些事件可能是合法的,但大多数情况下,它们是设计问题的结果。

一些编译器,例如GCC,有一个命令行选项来激活“警告为错误”模式。这是一个很好的工具,如果残酷,教育新手。

将警告视为错误只是自律的一种方式:您正在编译一个程序来测试那个闪亮的新功能,但是在您修复那些草率的部分之前,您无法测试。werror提供了其他信息。它只是非常明确地设定了优先级:

在修复现有代码中的问题之前,不要添加新代码

重要的是心态,而不是工具。编译器诊断输出是一种工具。MISRA C(嵌入式C)是另一个工具。使用哪一种并不重要,但可以说编译器警告是最简单的工具(只需设置一个标志),而且信噪比非常高。所以没有理由不使用它。

No tool is infallible. If you write const float pi = 3.14;, most tools won't tell you that you defined π with a bad precision which may lead to problems down the road. Most tools won't raise an eyebrow on if(tmp < 42), even if it's commonly known that giving variables meaningless names and using magic numbers is a way to disaster in big projects. You have to understand that any "quick test" code you write is just that: a test, and you have to get it right before you move on to other tasks, while you still see its shortcomings. If you leave that code as is, debugging it after you spend two months adding new features will be significantly harder.

一旦你进入了正确的心态,使用-Werror就没有意义了。将警告作为警告将允许您做出明智的决定,是否仍然有意义运行您即将开始的调试会话,还是中止它并首先修复警告。

其他的回答都很好,我不想重复他们说过的话。

One other aspect to "why enable warnings" that hasn't properly been touched on is that they help enormously with code maintenance. When you write a program of significant size, it becomes impossible to keep the whole thing in your head at once. You typically have a function or three that you're actively writing and thinking about, and perhaps a file or three on your screen that you can refer to, but the bulk of the program exists in the background somewhere and you have to trust that it keeps working.

如果你改变的某些东西给你看不见的东西带来了麻烦,你就会提醒自己。

例如Clang警告-Wswitch-enum。如果您在枚举上使用开关而漏掉了一个可能的枚举值,则会触发警告。您可能认为这是一个不太可能犯的错误:在编写switch语句时,您可能至少查看了枚举值列表。您甚至可能有一个IDE为您生成开关选项,不为人为错误留下任何空间。

六个月后,当您向枚举中添加另一个可能的条目时,这个警告才真正发挥作用。同样,如果您正在考虑所讨论的代码,那么您可能不会有问题。但是如果这个枚举用于多个不同的目的,并且它是用于您需要额外选项的其中一个目的,那么很容易忘记更新您六个月没有接触过的文件中的开关。

You can think of warnings in the same way as you'd think of automated test cases: they help you make sure that the code is sensible and doing what you need when you first write it, but they help even more to make sure that it keeps doing what you need while you prod at it. The difference is that test cases work very narrowly to the requirements of your code and you have to write them, while warnings work broadly to sensible standards for almost all code, and they're very generously supplied by the boffins who make the compilers.

将警告视为错误只有一个问题:当你使用来自其他来源的代码时(例如,微软库,开源项目),他们没有正确地完成他们的工作,编译他们的代码会产生大量的警告。

在编写代码时,我总是确保它不会产生任何警告或错误,并在编译之前进行清理,而不会产生任何无关的噪音。我不得不处理的垃圾让我感到震惊,当我不得不构建一个大项目时,看着一串警告从编译器应该只声明它处理了哪些文件的地方经过时,我感到震惊。

我也记录我的代码,因为我知道软件真正的生命周期成本主要来自维护,而不是最初的编写,但这是另一回事……

警告包含了一些最熟练的c++开发人员可以放入应用程序中的最佳建议。他们值得留在身边。

C++, being a Turing complete language, has plenty of cases where the compiler must simply trust that you knew what you are doing. However, there are many cases where the compiler can realize that you probably did not intend to write what you wrote. A classic example is printf() codes which don't match the arguments, or std::strings passed to printf (not that that ever happens to me!). In these cases, the code you wrote is not an error. It is a valid C++ expression with a valid interpretation for the compiler to act on. But the compiler has a strong hunch that you simply overlooked something which is easy for a modern compiler to detect. These are warnings. They are things that are obvious to a compiler, using all the strict rules of C++ at its disposal, that you might have overlooked.

关闭或忽略警告,就像选择忽略那些比你更有经验的人的免费建议。这是一个傲慢的教训,当你飞得离太阳太近,翅膀融化了,或者发生记忆损坏错误时,这个教训就结束了。在这两者之间,我愿意随时从天上掉下来!

“将警告视为错误”是这一哲学的极端版本。这里的想法是解决编译器给您的每个警告——您听取每个免费建议并执行它。这对你来说是否是一个好的开发模式取决于你的团队以及你所开发的产品类型。这是僧侣可能有的苦行方式。对一些人来说,效果很好。对另一些人来说,则不然。

在我的许多应用程序中,我们不将警告视为错误。我们这样做是因为这些特定的应用程序需要在多个平台上编译,使用多个不同年代的编译器。有时我们会发现,如果在一个平台上修复一个警告,而不将其转化为另一个平台上的警告,实际上是不可能的。所以我们只是小心行事。我们尊重警告,但我们不会为它们竭尽全力。