为什么我应该始终启用编译器警告?

Why should I always enable compiler warnings?

提问人:n. m. could be an AI 提问时间:9/8/2019 最后编辑:S.S. Annen. m. could be an AI 更新时间:7/20/2023 访问量:40936

问:

我经常听到在编译 C 和 C++ 程序时,我应该“始终启用编译器警告”。为什么有必要这样做?我该怎么做?

有时我还听说我应该“将警告视为错误”。我应该吗?我该怎么做?

C C 编译器 警告 C++-FAQ

评论

1赞 Thorbjørn Ravn Andersen 5/31/2022
所有的答案都假设是善意的。对于恶意行为,请看一下 Underhanded C 比赛。underhanded-c.org

答:

387赞 n. m. could be an AI 9/8/2019 #1

为什么要启用警告?

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

  • 忘记初始化变量
  • 忘记函数中的值return
  • 参数和族与格式字符串不匹配printfscanf
  • 函数无需事先声明即可使用(仅限 C)

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

如何启用警告?

这取决于您的编译器。

Microsoft C 和 C++ 编译器可以理解 、、 和 等开关。至少使用 . 并且可能会对系统头文件发出虚假警告,但如果您的项目使用这些选项之一进行干净的编译,那就去做吧。这些选项是互斥的。/W1/W2/W3/W4/Wall/W3/W4/Wall

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

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

为什么应将警告视为错误?它们只是警告!

编译器警告表示代码中存在潜在的严重问题。上面列出的问题几乎总是致命的;其他人可能是也可能不是,但你希望编译失败,即使它被证明是一个误报。调查每个警告,找到根本原因,然后进行修复。如果出现误报,请解决它,即使用不同的语言功能或构造,以便不再触发警告。如果这被证明是非常困难的,请根据具体情况禁用该特定警告。

您不想只是将警告作为警告,即使所有这些警告都是误报。对于发出的警告总数少于 7 的非常小的项目来说,这可能是可以的。除此之外,新的警告很容易迷失在熟悉的旧警告中。不允许那样做。只需使所有项目都干净地编译即可。

请注意,这适用于程序开发。如果您以源代码形式向世界发布您的项目,那么最好不要在已发布的构建脚本中提供 -Werror 或等效项。用户可能会尝试使用不同版本的编译器或完全不同的编译器来生成项目,这可能会启用一组不同的警告。您可能希望他们的构建成功。保持警告处于启用状态仍然是一个好主意,这样看到警告消息的人就可以向您发送错误报告或补丁。

如何将警告视为错误?

这同样是通过编译器开关完成的。 是针对Microsoft的,其他大多数都使用.无论哪种情况,如果生成任何警告,编译都将失败。/WX-Werror

这够了吗?

可能不是!随着优化水平的提高,编译器开始越来越仔细地查看代码,这种更仔细的审查可能会发现更多的错误。因此,不要满足于警告开关本身,在启用优化的情况下进行编译时,请始终使用它们(或 ,或者如果使用 MSVC)。-O2-O3/O2

评论

122赞 n. m. could be an AI 9/8/2019
我之所以发布这个问答,是因为我厌倦了告诉人们启用警告。现在我可以把他们指出来(或者,如果我的心情特别糟糕,就把他们的问题当作骗子来结束)。欢迎您改进此答案或添加自己的答案!
18赞 pmg 9/8/2019
你也可以使用 clang 的 -Weverything
9赞 Kyle A 9/9/2019
我唯一要添加的修饰符是,某些警告可能对您的应用程序没有帮助。(我看到过编译器在结构中的元素之间添加了 2 个字节的填充的警告。该应用程序用于原型设计,因此浪费的内存不会打扰我们。将所有警告视为错误,然后仅在知道警告对您没有帮助的原因时才禁用警告。
24赞 interfect 9/10/2019
对于遵循默认生成说明的用户来说,将警告视为错误的好处是,当编译器添加新警告时,您的代码会腐烂。下载您的代码并尝试在将来构建它的用户可能无法这样做,因为他们的编译器太新了,并且会发出有关某些额外括号或编译器不关心的内容的警告。遇到错误的用户不对代码或生成系统负责,并且不知道如何关闭将警告视为错误并实际生成项目。
17赞 n. m. could be an AI 9/11/2019
@interfect 是的,这种情况在我身上发生过几次。没什么大不了的。如果你选择使用从未测试过的编译器来构建一个未维护的软件,那么,你最好准备好自己做一些维护
104赞 Steve Summit 9/8/2019 #2

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

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

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

但是在 C 和 C++ 中,你可以做的大量事情是“坏主意”,但并不是正式的“违反规则”。有时它们在某些时候是一个坏主意(但有时可能是站得住脚的);有时,它们几乎一直都是一个坏主意。但传统一直是对这些事情发出警告——因为,再一次,假设程序员知道他们在做什么,他们不会在没有充分理由的情况下做这些事情,他们会被一堆不必要的警告所惹恼。

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

最后,经验表明,不仅程序员确实会犯错误,而且这些错误可能会产生真正的严重后果。如果你犯了一个错误,而编译器没有警告你,并且不知何故,程序不会立即崩溃或因此而做一些明显错误的事情,那么这个错误就会潜伏在那里,隐藏起来,有时长达数年,直到它造成一个非常大的问题。

所以事实证明,大多数时候,警告毕竟是个好主意。即使是有经验的程序员也已经了解到这一点(实际上,“尤其是有经验的程序员已经学会了这一点”),总的来说,警告往往利大于弊。因为每次你故意做错事并且警告是令人讨厌的,可能至少有十次你不小心做错了什么,警告使你免于进一步的麻烦。大多数警告都可以在您真正想做“错误”事情的那几次被禁用或解决。

(这种“错误”的一个典型例子是测试。大多数时候,这确实是一个错误,所以现在大多数编译器都会警告它——有些甚至是默认的。但是,如果您确实想同时分配和测试结果,则可以通过键入 来禁用警告。if(a = b)baif((a = b))

第二个问题是,为什么要要求编译器将警告视为错误?我想说这是因为人性,特别是说“哦,这只是一个警告,那不是那么重要,我稍后会清理它”的太容易的反应了。但是,如果你是一个拖延症患者(我不了解你,但我是一个世界级的拖延症患者),那么基本上很容易推迟必要的清理工作——如果你养成了忽视警告的习惯,那么就会越来越容易错过一个重要的警告信息,这些信息就坐在那里,没有被注意到,在你无情地忽视的所有警告信息中。

因此,要求编译器将警告视为错误是你可以自己玩的一个小把戏,以绕过这个人为的弱点,强迫自己今天修复警告,否则你的程序将无法编译。

就我个人而言,我并不坚持将警告视为错误——事实上,如果我说实话,我可以说我不倾向于在我的“个人”编程中启用该选项。但你可以肯定,我已经在工作中启用了这个选项,我们的风格指南(我写的)要求使用它。我想说的是——我怀疑大多数专业程序员都会说——任何不将警告视为 C 语言错误的商店都是不负责任的行为,没有遵守普遍接受的行业最佳实践。

评论

9赞 Dancrumb 9/10/2019
“知道自己在做什么的程序员”——哈哈;有一个“没有真正的苏格兰人”的谬误,如果我见过一个:)
3赞 Steve Summit 9/10/2019
@Dancrumb 大声笑回到阿查。我从来都不太确定我是否理解“没有真正的苏格兰人谬误”,但我喜欢它,所以这对我来说是一个很好的练习。我猜这里的应用程序是这样的:“没有 C 程序员会写,因此我们不需要警告它。(然后有人在 10 个已发布的产品中生成一个列表,其中包含由此特定错误导致的 10 个严重错误。“好吧,没有一个有经验的C程序员会写这个......”if(a = b)
3赞 alephzero 9/10/2019
@SteveSummit但一个真正有经验的 C 程序员可能会编写并认真编写,在一个地方捕获和测试代码(假设唯一的目的是产生副作用!一个真正有经验的程序员可能知道这不是一个好的编码风格,这一事实是无关紧要的;)if (returnCodeFromFoo = foo(bar))foo
6赞 Flydog57 9/10/2019
问题是,大多数非常有经验的程序员都会启用大多数(如果不是全部)警告。如果他们确实想使用类似 的东西,那么他们会添加注释并关闭警告(这样当维护程序员在 4 年后看到它时,他/她会意识到代码是故意的。也就是说,我曾与(Microsoft C++国)坚持认为将 /Wall 与将警告视为错误相结合的人一起工作是要走的路。呃,不是(除非你想放很多压制评论)。if (returnCodeFromFoo = foo(bar))
3赞 Peter Smith 9/10/2019
作为一个每天写代码的人,但当我代码时,它往往是裸机的(通常在我自己设计的板上),我发现警告是无价的。将值填充到内部寄存器中时(例如,对于 DMA 描述符位置),有关转换为指针的警告意味着我执行强制转换以清除警告。这不会是一个错误,但如果其他人(甚至我自己!)在几个月内拿起该代码,那很可能会令人困惑。此外,我还将无警告规则应用于我的 CAD 工具的输出。
3赞 Swift - Friday Pie 9/9/2019 #3

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

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

-37赞 sqr163 9/9/2019 #4

放轻松:你不必,没有必要。-Wall-Werror 是由代码重构狂人为自己设计的:它是由编译器开发人员发明的,以避免在用户端的编译器或编程语言更新后破坏现有构建。该功能什么都不是,而是关于中断或不中断构建的决定。

使用与否完全取决于您的喜好。我一直在使用它,因为它可以帮助我纠正错误。

评论

20赞 Spikatrix 9/9/2019
虽然不是强制性的,但强烈建议使用它们
21赞 YSC 9/10/2019
-Wall and -Werror was designed by code-refactoring maniacs for themselves.[需要引证]
25赞 scohe001 9/10/2019
你似乎在自相矛盾。如果你“一直使用它,因为它有助于纠正[你的]错误”,难道不值得教给新程序员,这样他们从一开始就到处都这样做吗?我不认为这个问题是在问是否可以在没有 和 的情况下进行编译,它只是在问它是否是一个好主意。从你的最后一句话来看,听起来你是在说。-Wall-Werror
15赞 Thorbjørn Ravn Andersen 9/10/2019
当你在维护非你自己编写的代码方面获得更多经验时,请重新审视这个答案。
3赞 Anders 10/6/2019
这不是一个有用的答案。OPs 问题中有 4 个问号。这个回复回答了多少?
42赞 Cort Ammon 9/9/2019 #5

警告包括一些最熟练的 C++ 开发人员可以烘焙到应用程序中的最佳建议。他们值得留在身边。

C++ 是一种图灵完备语言,在很多情况下,编译器必须简单地相信你知道你在做什么。但是,在许多情况下,编译器可以意识到您可能不打算编写所写的内容。一个典型的例子是与参数不匹配的 printf() 代码,或者传递给 printfstd::strings(这不会发生在我身上!在这些情况下,您编写的代码不是错误。它是一个有效的 C++ 表达式,具有供编译器执行操作的有效解释。但是编译器有一种强烈的预感,你只是忽略了一些现代编译器很容易检测到的东西。这些是警告。它们是编译器显而易见的东西,使用其可以使用的所有严格的 C++ 规则,您可能忽略了这些规则。

关闭警告或忽略警告,就像选择忽略那些比你更熟练的人的免费建议一样。这是傲慢的一课,当你飞得离太阳太近而你的翅膀融化,或者发生内存损坏错误时,就会结束。在两者之间,我随时都会从天上掉下来!

“将警告视为错误”是这一理念的极端版本。这里的想法是,你解决了编译器给你的每一个警告——你听取了每一点免费建议并采取行动。这是否是你的开发模式,取决于团队和你正在开发的产品类型。这是僧侣可能拥有的苦行方法。对于某些人来说,它效果很好。对于其他人来说,它没有。

在我的许多应用程序中,我们不会将警告视为错误。我们这样做是因为这些特定的应用程序需要在多个平台上使用多个不同年龄的编译器进行编译。有时我们发现,实际上不可能在一侧修复警告,而不会在另一个平台上变成警告。所以我们只是小心翼翼。我们尊重警告,但我们不会为它们向后弯腰。

评论

13赞 Kami Kaze 9/9/2019
C++ 是图灵完备的与此有关。很多语言都是图灵完备的,如果你做错了什么,不要相信你。
4赞 Caleth 9/9/2019
@KamiKaze每种语言都会有惯用错误(例如,Java 无法阻止你编写不一致的 / ),并且报告哪些错误是实现质量问题。equalshashCode
2赞 Cort Ammon 9/10/2019
@KamiKaze 图灵完备性位表明,在某些情况下,编译器无法证明您的代码将无法按计划工作。这很重要,因为编译器不能使所有“错误”代码都成为错误。错误只能保留给语言设计者确定永远是“错误”的行为。(通常是因为它导致不一致的路径)。
2赞 Cort Ammon 9/10/2019
这也指出了“所有警告都是错误”的挑战。从设计上讲,警告更具机会性,它触发一些可能正确的代码,以换取更频繁地触发错误的代码。错误警告会导致您无法使用完整语言的功能。
2赞 Cort Ammon 10/2/2019
然而,一般来说,程序员想要一种不仅仅是“安全”事情的语言。我们想要一种语言,它能做我们认为我们告诉它做的事情。因此,警告仍然很重要,因为我们希望计算机执行的实际操作类是语义类。编译器可以通过定义“不正确”或“不安全”来挑剔它,但最终你仍然有一个程序员希望程序执行的行为的超类。警告有助于缩小该超类的范围。
22赞 RedSonja 9/9/2019 #6

处理警告不仅可以生成更好的代码,还可以使您成为更好的程序员。警告会告诉你一些今天对你来说似乎微不足道的事情,但总有一天这个坏习惯会卷土重来,咬掉你的头。

使用正确的类型,返回该值,计算该返回值。花点时间思考一下,“在这种情况下,这真的是正确的类型吗?“我需要退货吗?”还有大人物;“这个代码会在未来10年内被移植吗?”

首先养成编写无警告代码的习惯。

16赞 Dmitry Grigoryev 9/9/2019 #7

将警告视为错误只是一种自律的手段:您正在编译一个程序来测试这个闪亮的新功能,但在您修复草率的部分之前,您不能这样做。Werror 没有提供其他信息。它只是非常清楚地设置了优先级:

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

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

没有任何工具是万无一失的。如果你写,大多数工具不会告诉你你定义的π精度很差,这可能会导致未来的问题。大多数工具不会引起人们的注意,即使众所周知,在大型项目中给变量起无意义的名称和使用幻数是导致灾难的一种方式。你必须明白,你编写的任何“快速测试”代码都只是一个测试,在你继续执行其他任务之前,你必须把它做好,而你仍然看到它的缺点。如果保持该代码不变,则在花费两个月时间添加新功能后对其进行调试将更加困难。const float pi = 3.14;if(tmp < 42)

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

评论

3赞 emk 10/4/2019
不管是好是坏,Rust 的 linting 工具实际上会警告常数 “3.14”。这实际上是文档中的一个例子。但正如您可能从名称中猜到的那样,以积极提供帮助而自豪。clippyclippy
1赞 Dmitry Grigoryev 10/5/2019
@emk 谢谢你这个例子,也许我应该以一种永不言败的方式重新表述我的答案。我并不是说检查不精确的π值是不可能的,只是仅仅删除警告并不能保证良好的代码质量。
0赞 Móż 10/5/2019
错误警告会给你的一件事是,自动构建将失败,从而提醒你出了问题。自动构建还允许自动 linting(爆炸为 3...2...1.. :)
6赞 S.S. Anne 9/9/2019 #8

您应该始终启用编译器警告,因为编译器通常可以告诉您代码出了什么问题。为此,请将 -Wall -Wextra 传递给编译器。

通常应将警告视为错误,因为警告通常表示代码有问题。但是,通常很容易忽略这些错误。因此,将它们视为错误将导致生成失败,因此您不能忽略这些错误。若要将警告视为错误,请将 -Werror 传递给编译器。

21赞 gsamaras 9/10/2019 #9

非固定警告迟早会导致代码中出现错误


例如,调试分段错误需要程序员跟踪故障的根源(原因),该根源通常位于代码中与最终导致分段错误的行相比的先前位置。

非常典型的情况是,原因是编译器发出了警告但您忽略了该行,而导致分段错误的行是最终引发错误的行。

修复警告会导致解决问题...经典之作!

上述的演示...请考虑以下代码:

#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 伊壁鸠鲁教授曾经说过的那样:

分段故障

为了在实际场景中对此进行调试,需要从导致分段故障的线路开始,并尝试追踪原因的根源......他们将不得不搜索那边大量代码发生了什么......istr

直到有一天,他们发现自己处于这样的情况:他们发现该字符串未初始化,因此它具有垃圾值,这会导致索引字符串(方式)超出其边界,从而导致分割错误。idx

如果他们没有忽略警告,他们就会立即发现这个错误!

评论

5赞 Marc van Leeuwen 9/12/2019
到你的标题:不一定。例如,建议在实际上不需要括号的公式中使用括号的警告指向一个永远不会导致错误的非问题。给定编程语言中的运算符优先级不会更改。曾。
4赞 Hubert Jasieniecki 9/12/2019
@MarcvanLeeuwen 您引用的实例可能会变成错误,例如,如果不记得运算符优先级的程序员正确地修改了公式。警告告诉您:“在某些时候某人可能不清楚,添加一些括号以使其更清楚”。尽管人们必须同意原始帖子的标题并不总是正确的。
0赞 Jim Balter 10/5/2019
^ 任何事情都可以变成错误。将 bug 引入部分括号代码和完全带括号的代码一样容易。
2赞 celtschk 12/19/2019
如果您遇到分段错误,那您很幸运。如果你不太幸运,你可能碰巧是你在测试中预期的值(如果预期值为 0,则不太可能),并且实际上恰好指向一些在部署时永远不应该打印的敏感数据。idx
1赞 Peter Mortensen 11/16/2021
“知识产权伊壁鸠鲁教授”中的“IP”是什么?最接近的是知识产权,但这不符合上下文。“P”代表“哲学”?“P”代表“程序”还是“编程”“互联网编程”物联网编程”?你是说PI(首席研究员)吗?还是别的什么?
7赞 Joshua 9/10/2019 #10

这是对 C 的具体回答,也是为什么这对 C 来说比其他任何事情都重要得多。

#include <stdio.h>

int main()
{
   FILE *fp = "some string";
}

此代码编译时显示警告。在地球上几乎所有其他语言(汇编语言除外)中,错误都是 C 语言中的警告,而 C 语言中的警告几乎总是伪装的错误。警告应该是固定的,而不是禁止的。

使用 GCC,我们将其作为 .gcc -Wall -Werror

这也是对一些Microsoft不安全的API警告高度咆哮的原因。大多数编写 C 语言的人都学会了将警告视为错误的艰难方法,而这些东西似乎不是同一种东西,并且需要不可移植的修复程序。

20赞 Josiah 9/10/2019 #11

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

“为什么要启用警告”的另一个方面没有被正确触及,那就是它们对代码维护有很大帮助。当你编写一个相当大的程序时,不可能一次把整个事情放在你的脑海中。你通常有一个或三个你正在积极编写和思考的功能,也许你的屏幕上有一三个文件可以参考,但大部分程序存在于后台的某个地方,你必须相信它继续工作。

打开警告,并尽可能精力充沛地出现在你的脸上,有助于提醒你,如果你改变的东西给你看不见的东西带来麻烦。

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

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

你可以像思考自动化测试用例一样思考警告:它们帮助你确保代码是合理的,并在你第一次编写代码时做你需要的事情,但它们更有助于确保它继续做你需要的事情,而你正在推动它。不同之处在于,测试用例的工作范围非常狭窄,你必须编写它们,而警告则广泛地适用于几乎所有代码的合理标准,并且它们由制作编译器的棺材非常慷慨地提供。

评论

9赞 Robin Bennett 9/10/2019
它们帮助维护的另一种方式是,当您查看其他人的代码并且无法判断副作用是否是故意的时。打开警告后,您知道他们至少知道了这个问题。
0赞 Móż 10/5/2019
或者,在我的例子中,您从嵌入式系统导入一个文件,其中包含一个 3000+ 行开关语句,该语句位于具有数千个值的枚举上。“失败”警告(通过使用 goto 避免)掩盖了许多“未处理”的错误......嵌入式编译器没有发出其中任何一个,但这些错误仍然很重要。
10赞 Dom 9/10/2019 #12

作为使用传统嵌入式 C 代码的人,启用编译器警告有助于在提出修复建议时显示许多弱点和需要调查的领域。在 GCC 中,使用 -Wall 和 -Wextra 甚至 -Wshadow 变得至关重要。我不打算逐一列举,但我会列出一些突然出现的危险,这些危险有助于显示代码问题。

变量被抛在后面

这很容易指出未完成的工作和可能没有使用所有传递变量的区域,这可能是一个问题。让我们看一个可能触发此情况的简单函数:

int foo(int a, int b)
{
   int c = 0;

   if (a > 0)
   {
        return a;
   }
   return 0;
}

只是在没有或返回没有问题的情况下编译它。 会告诉你,尽管它从未使用过:-Wall-Wextra-Wallc

foo.c:在函数“foo”中:

foo.c:9:20:警告:未使用的变量“c” [-Wunused-变量]

-Wextra还会告诉你,你的参数不做任何事情:b

foo.c:在函数“foo”中:

foo.c:9:20:警告:未使用的变量“c” [-Wunused-变量]

foo.c:7:20:警告:未使用的参数“b”[-Wunused-parameter] int foo(int a, int b)

全局变量阴影

这个有点硬,直到使用才出现。让我们修改上面的示例以添加,但恰好有一个与本地同名的全局,这在尝试同时使用两者时会引起很多混淆。-Wshadow

int c = 7;

int foo(int a, int b)
{
   int c = a + b;
   return c;
}

打开后,很容易发现此问题。-Wshadow

foo.c:11:9:警告:“C”的声明会掩盖全局声明 [-影子]

foo.c:1:5:注意:阴影声明在这里

设置字符串格式

这不需要 GCC 中的任何额外标志,但它在过去仍然是问题的根源。尝试打印数据但出现格式错误的简单函数可能如下所示:

void foo(const char * str)
{
    printf("str = %d\n", str);
}

这不会打印字符串,因为格式标志是错误的,GCC 会很高兴地告诉你这可能不是你想要的:

foo.c:在函数“foo”中:

foo.c:10:12:警告:格式“%d”需要 类型为“int”的参数,但参数 2 的类型为“const char *” [-Wformat=]


这些只是编译器可以为您仔细检查的众多内容中的三项。还有很多其他的,比如使用其他人指出的未初始化的变量。

评论

7赞 Mawg says reinstate Monica 9/10/2019
在嵌入式世界中,我最担心的警告是“”和“”警告。我发现很难理解有多少“程序员”忽略了这些(事实上,我真的不确定为什么它们不是错误)possible loss of precisioncomparison between signed and unsigned
3赞 Justin Time - Reinstate Monica 9/11/2019
@Mawg,在后一种情况下,我相信它不是错误的主要原因是 的结果是无符号的,但默认的整数类型是有符号的。结果类型 , 通常用于与类型大小相关的任何内容,例如对齐方式或数组/容器元素计数,而整数通常用作 “除非另有要求”。考虑到有多少人因此被教导用来迭代他们的容器(与 相比),将其设置为错误将破坏几乎所有内容。;Psizeofsizeofsize_tintintintsize_t
5赞 Fizik26 9/24/2019 #13

由于某些原因,C++ 中的编译器警告非常有用。

  1. 它允许您向您展示您可能在哪些方面犯了错误,这可能会影响您的操作的最终结果。例如,如果您没有初始化变量,或者您使用“=”而不是“==”(只是示例)

  2. 它还允许您显示您的代码不符合 C++ 标准的地方。这很有用,因为例如,如果代码符合实际标准,则很容易将代码移动到其他平台。

通常,警告对于显示代码中存在错误的位置非常有用,这些错误可能会影响算法的结果或防止用户使用程序时出现错误。

-2赞 Hasan Raza 10/1/2019 #14

您绝对应该启用编译器警告,因为某些编译器不擅长报告一些常见的编程错误,包括以下内容:

  • 初始化变量被遗忘
  • 从函数 get missed 返回值
  • printf 和 scanf 系列中的简单参数与格式字符串不匹配
  • 函数无需事先声明即可使用,尽管这仅在 C 中发生

因此,由于这些功能可以被检测和报告,只是通常不是默认的;因此,必须通过编译器选项显式请求此功能。

5赞 josyb 10/2/2019 #15

警告是等待发生的错误。 因此,您必须启用编译器警告并整理代码以删除任何警告。

5赞 Kirk Augustin 10/3/2019 #16

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

编译器输出越多,任何人注意到或打扰的次数就越少。越干净越好。这也意味着你知道自己在做什么。警告是非常不专业、粗心和冒险的。

4赞 FKEinternet 10/3/2019 #17

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

我总是编写我的代码,这样它就不会产生任何警告或错误,并清理它,直到它编译时不产生任何外来的噪音。我必须处理的垃圾让我感到震惊,当我不得不构建一个大项目并看到一连串的警告时,我感到很震惊,编译应该只宣布它处理了哪些文件。

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

评论

0赞 Móż 10/5/2019
不要敲它,对于那些可以向客户大声朗读编译器警告的人来说,咨询工作是有大钱的。
0赞 celtschk 12/19/2019
来自其他来源的代码生成警告并不一定意味着作者草率。这也可能意味着他们使用不同的编译器编译代码,从而生成了一组不同的警告。代码可以在一个编译器上编译时不发出警告,而在另一个编译器上生成警告。或者,它可能只是一组不同的警告选项;例如,他们使用,而您使用 .-Wall-Wall -Wextra
7赞 Mark Diaz 10/4/2019 #18

编译器警告是你的朋友

我在传统的 Fortran 77 系统上工作。编译器告诉我有价值的事情:子例程调用中的参数数据类型不匹配,以及如果我有未使用的变量或子例程参数,则在将值设置到变量之前使用局部变量。这些几乎总是错误。

当我的代码干净地编译时,97% 它都可以工作。与我一起工作的另一个人在关闭所有警告的情况下进行编译,在调试器中花费数小时或数天,然后请我帮忙。我只是在打开警告的情况下编译他的代码,并告诉他要修复什么。

3赞 Jason Livesay 10/4/2019 #19

C++编译器接受编译代码,这显然会导致未定义的行为,这是编译器的一个主要缺陷。他们不解决这个问题的原因是这样做可能会破坏一些可用的构建。

大多数警告应该是阻止生成完成的致命错误。仅显示错误并无论如何都进行构建的默认值是错误的,如果您不覆盖它们以将警告视为错误并留下一些警告,那么您最终可能会导致程序崩溃并随机执行操作。

评论

2赞 Justin Time - Reinstate Monica 10/6/2019
具有讽刺意味的是,许多未定义的行为实际上并不会引起警告,而是默默地编译成一个令人讨厌的小定时炸弹。;P
1赞 celtschk 12/19/2019
问题在于,如果标准要求错误消息,则必须在出现问题的所有情况下发出该错误消息,但在问题未发生时绝不会发出该错误消息。但在像未定义行为这样的情况下,这可能是不可能的。例如,请考虑以下代码: 当且仅当两者兼而有之时,此代码才会表现出未定义的行为,并且可以在同一函数执行时返回。这可能是真的,也可能不是,但编译器如何判断呢?int i; if (fun1()) i=2; if (fun2()) i=3; char s="abcde"[i];fun1()fun2()false
0赞 klutt 6/22/2020
虽然 UB 并非没有问题,但从某种意义上说,它仍然是一个功能。它允许编译器进行原本无法进行的优化。每次访问数组时,Java 代码都需要执行边界检查,从而导致代码速度变慢。但是,我确实同意应该重新指定 UB 的大部分以强制崩溃。
6赞 Jim In Texas 10/4/2019 #20

我曾经在一家制造电子测试设备的大型(财富 50 强)公司工作。

我们团队的核心产品是一个MFC程序,多年来,它产生了数百个警告。这在几乎所有情况下都被忽略了。

当错误发生时,这是一场令人毛骨悚然的噩梦。

在那个职位之后,我很幸运地被聘为一家新创业公司的第一位开发人员。

我鼓励对所有构建都采用“无警告”策略,将编译器警告级别设置为非常嘈杂。

我们的做法是使用 #pragma 警告 - 推送/禁用/弹出开发人员确定确实没问题的代码,以及调试级别的日志语句,以防万一。

这种做法对我们来说效果很好。

评论

1赞 Justin Time - Reinstate Monica 10/27/2019
借调。 它不仅抑制警告,还具有双重目的,即快速向其他程序员传达某些事情是故意的而不是偶然的,并充当搜索标签,用于在出现问题时快速定位潜在问题区域,但修复错误/警告并不能修复它。#pragma warning
0赞 Jim In Texas 10/30/2019
你是对的,贾斯汀,这正是我看待 #pragma 警告的方式
0赞 Peter Mortensen 11/20/2021
回复“财富 50 强”:你是说财富 500 强吗?
0赞 artless noise 7/20/2023 #21

所有百分比都偏离了现实,并不意味着要认真对待。

99% 的警告对于正确性完全没有用。但是,这 1% 会使您的代码无法正常工作(通常在极少数情况下)。重要的是,其他答案没有。

  1. 警告来自编译器开发人员。有一个“C”标准和一致性。但警告是编译器开发人员发出的信号,表明你给他们带来了问题。也就是说,这些可能是编译器编写者知道导致低效或错误构造的事情。这就像无视水管工说你不能在那里放厕所,然后告诉他们无论如何都要这样做。

  2. 下一个启用警告的人会认为你不称职,因为你没有启用警告。他们不知道 99% 的代码是正确的,并认为只有 50% 是正确的。

  3. 另一个经常被警告捕获的问题是死代码。即,永远不能做任何事情的代码。这可能是人们讨厌继承带有警告的代码的原因。他们所看到的 75% 的内容可能是无用的。

无警告代码让其他人相信代码是可移植的,并且能够适应工具、代码更新和一般的位腐烂。无警告代码让其他开发人员相信他们正在查看的代码不是疯狂的意大利面条或微妙的 boloney。他们也可能只是发现一两个错误。