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

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


当前回答

将警告视为错误只是自律的一种方式:您正在编译一个程序来测试那个闪亮的新功能,但是在您修复那些草率的部分之前,您无法测试。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就没有意义了。将警告作为警告将允许您做出明智的决定,是否仍然有意义运行您即将开始的调试会话,还是中止它并首先修复警告。

其他回答

忽略警告意味着您留下了草率的代码,这不仅会在将来给其他人带来问题,而且还会使您不太注意到重要的编译消息。

编译器输出越多,就越不会有人注意到。越干净越好。这也意味着你知道自己在做什么。警告是非常不专业、粗心和危险的。

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

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

众所周知,就hll而言,C是一种相当低级的语言。虽然c++看起来是一种比C高级得多的语言,但它仍然具有C的一些特征。其中一个特点就是这些语言是由程序员设计的,为程序员设计的——特别是那些知道自己在做什么的程序员。

(对于这个答案的其余部分,我将专注于C。我要说的大部分内容也适用于c++,尽管可能没有那么强烈。尽管Bjarne Stroustrup有一句名言:“C很容易搬起石头砸自己的脚;c++让它变得更难,但当你这样做的时候,它会把你的整条腿都炸掉。”)

如果你知道你在做什么——真的知道你在做什么——有时候你可能不得不“打破规则”。但大多数时候,我们大多数人都会同意,善意的规则让我们所有人都远离麻烦,一直肆意违反这些规则是一个坏主意。

But in C and C++, there are surprisingly large numbers of things you can do that are "bad ideas", but which aren't formally "against the rules". Sometimes they're a bad idea some of the time (but might be defensible other times); sometimes they're a bad idea virtually all of the time. But the tradition has always been not to warn about these things — because, again, the assumption is that programmers know what they are doing, they wouldn't be doing these things without a good reason, and they'd be annoyed by a bunch of unnecessary warnings.

当然,并不是所有的程序员都知道自己在做什么。特别是,每个C程序员(无论多么有经验)都要经历一个开始C程序员的阶段。即使是有经验的C程序员也会粗心大意,犯错误。

最后,经验表明,程序员不仅会犯错误,而且这些错误会产生真正的、严重的后果。如果你犯了一个错误,编译器没有警告你,而且程序没有立即崩溃,或者因为它而做一些明显的错误,这个错误可能潜伏在那里,隐藏,有时长达数年,直到它造成一个真正的大问题。

So it turns out that, most of the time, warnings are a good idea, after all. Even the experienced programmers have learned that (actually, it's "especially the experienced programmers have learned that"), on balance, the warnings tend to do more good than harm. For every time you did something wrong deliberately and the warning was a nuisance, there are probably at least ten times you did something wrong by accident and the warning saved you from further trouble. And most warnings can be disabled or worked around for those few times when you really want to do the "wrong" thing.

(这类“错误”的一个经典例子是if(A = b)的测试。大多数时候,这确实是一个错误,所以现在大多数编译器都会对此发出警告——有些甚至是默认的。但是如果你真的想把b赋值给a并测试结果,你可以通过输入if((a = b))来禁用警告。

The second question is, why would you want to ask the compiler to treat warnings as errors? I'd say it's because of human nature, specifically, the all-too-easy reaction of saying "Oh, that's just a warning, that's not so important, I'll clean that up later." But if you're a procrastinator (and I don't know about you, but I'm a world-class procrastinator) it's easy to put off the necessary cleanup for basically ever — and if you get into the habit of ignoring warnings, it gets easier and easier to miss an important warning message that's sitting there, unnoticed, in the midst of all the ones you're relentlessly ignoring.

因此,要求编译器将警告视为错误是你可以对自己玩的一个小技巧,以避免这个人类的弱点,迫使自己立即修复警告,否则你的程序将无法编译。

Personally, I'm not as insistent about treating warnings as errors — in fact, if I'm honest, I can say that I don't tend to enable that option in my "personal" programming. But you can be sure I've got that option enabled at work, where our style guide (which I wrote) mandates its use. And I would say — I suspect most professional programmers would say — that any shop that doesn't treat warnings as errors in C is behaving irresponsibly, is not adhering to commonly-accepted industry best practices.

非固定的警告迟早会导致代码中的错误。


例如,调试分段错误需要程序员追踪错误的根源(原因),它通常位于代码中比最终导致分段错误的行更前面的位置。

很典型的情况是,导致错误的行是编译器发出警告而你忽略的行,而导致分段错误的行是最终抛出错误的行。

修复警告就等于修复了问题……一个经典的!

以上的演示…考虑下面的代码:

#include <stdio.h>

int main(void) {
  char* str = "Hello, World!!";
  int idx;

  // Colossal amount of code here, irrelevant to 'idx'

  printf("%c\n", str[idx]);

  return 0;
}

当使用传递给GCC的"Wextra"标志进行编译时,给出:

main.c: In function 'main':
main.c:9:21: warning: 'idx' is used uninitialized in this function [-Wuninitialized]
    9 |   printf("%c\n", str[idx]);
      |                     ^

我可以忽略它,然后执行代码……然后我就会看到一个“重大”分割错误,就像我的IP伊壁鸠鲁教授曾经说过的那样:

段错误

为了在现实场景中调试这一点,人们将从导致分段错误的行开始,并试图跟踪原因的根源是什么……他们将不得不在那里的大量代码中搜索i和str发生了什么……

直到有一天,他们发现idx使用时没有初始化,因此它有一个垃圾值,这导致索引字符串(方式)超出其界限,从而导致分割错误。

如果他们没有忽视这个警告,他们就会立即发现这个漏洞!

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

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.