【问题标题】:Why should I always enable compiler warnings?为什么我应该总是启用编译器警告?
【发布时间】:2020-01-10 13:21:03
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c warnings compiler-warnings c++-faq


    【解决方案1】:

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

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

    【讨论】:

    • 虽然不是强制,但强烈推荐使用
    • -Wall and -Werror was designed by code-refactoring maniacs for themselves. [需要引用]
    • 你好像在自相矛盾。如果您“一直使用它,因为它有助于修复 [您的] 错误”,难道不值得向新程序员教授,以便他们从一开始就可以在任何地方使用它吗?我不认为这个问题是在问是否可能在没有-Wall-Werror 的情况下进行编译,它只是在问这是否是个好主意。哪一个,从你的最后一句话,听起来你说的是。
    • 当您在维护非您编写的代码方面获得更多经验时,请重新访问此答案。
    • 这不是一个有用的答案。 OPs 问题中有 4 个问号。这个回复回答了多少?
    【解决方案2】:

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

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

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

    一般来说,警告对于向您显示代码中的错误非常有用,这些错误可能会影响您的算法结果或防止用户使用您的程序时出现一些错误。

    【讨论】:

      【解决方案3】:

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

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

      当出现错误时,这是​​一场可怕的噩梦。

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

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

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

      这种做法对我们很有效。

      【讨论】:

      • 第二。 #pragma warning 不仅抑制警告,它还具有双重目的,即快速与其他程序员沟通某些事情是故意的而不是偶然的,并充当搜索标签,用于在出现问题时快速定位潜在问题区域但修复错误/警告没有解决它。
      • 你说得对,贾斯汀,这正是我对 #pragma 警告的看法
      • 回复“财富50”:你的意思是Fortune 500
      【解决方案4】:

      编译器警告是你的朋友

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

      当我的代码编译干净时,97% 的代码都能正常工作。与我一起工作的另一个人在编译时关闭所有警告,在调试器中花费数小时或数天,然后请我提供帮助。我只是编译他的代码并显示警告并告诉他要修复什么。

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        • 别敲了,咨询工作对于那些可以向客户大声朗读编译器警告的人来说是很划算的。
        • 来自其他来源的代码生成警告并不必要意味着作者马虎。这也可能意味着他们使用不同的编译器编译代码,生成了不同的警告集。代码可以在一个编译器上编译而没有警告,并在另一个编译器上生成警告。或者可能只是一组不同的警告选项;例如他们使用-Wall,而你使用-Wall -Wextra
        【解决方案6】:

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

        编译器输出越多,人们就越不会注意到或打扰。越清洁越好。这也意味着你知道你在做什么。警告是非常不专业、粗心和冒险的。

        【讨论】:

          【解决方案7】:

          您绝对应该启用编译器警告,因为某些编译器不善于报告一些常见的编程错误,包括:

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

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

          【讨论】:

            【解决方案8】:

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

            遗留变量

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

            int foo(int a, int b)
            {
               int c = 0;
            
               if (a > 0)
               {
                    return a;
               }
               return 0;
            }
            

            在没有-Wall-Wextra 的情况下编译它不会返回任何问题。 -Wall 会告诉你 c 从未被使用过:

            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' 的声明会影响全局声明 [-Wshadow]

            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=]


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

            【讨论】:

            • 在嵌入式世界中,我最担心的警告是“possible loss of precision”和“comparison between signed and unsigned”警告。我发现很难掌握有多少“程序员”忽略了这些(事实上,我不确定为什么它们不是错误)
            • 在后一种情况下,@Mawg,我认为这不是错误的主要原因是sizeof 的结果是无符号的,但默认的整数类型是有符号的。 sizeof 结果类型 size_t 通常用于与类型大小相关的任何内容,例如对齐或数组/容器元素计数,而整数通常用作“int”,除非另有说明必需的”。考虑到有多少人因此被教导使用int 来迭代他们的容器(比较intsize_t),让它成为一个错误会破坏一切。 ;P
            【解决方案9】:

            这是对 C 的特定答案,以及为什么这对 C 比对其他任何事物都重要。

            #include <stdio.h>
            
            int main()
            {
               FILE *fp = "some string";
            }
            

            此代码编译时带有警告。地球上几乎所有其他语言(除了汇编语言)中的错误和应该是错误都是 C 中的警告。C 中的警告几乎总是伪装的错误。警告应该被修复,而不是被禁止。

            使用 GCC,我们以 gcc -Wall -Werror 执行此操作。

            这也是一些微软非安全 API 警告的高调性的原因。大多数 C 语言编程人员已经学会了将警告视为错误的艰难方法,而这些东西似乎不是同一类东西,并且需要不可移植的修复。

            【讨论】:

              【解决方案10】:

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

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

              发出警告,并尽可能让它们充满活力并出现在您的脸上,这有助于在您更改的内容对您看不到的内容造成麻烦时提醒您。

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

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

              您可以像考虑自动化测试用例一样考虑警告:它们可以帮助您确保代码是合理的,并在您第一次编写代码时执行您需要的操作,但它们更有助于确保它在你刺激它的同时继续做你需要的事情。不同之处在于,测试用例非常严格地满足您的代码要求并且您必须编写它们,而警告则广泛适用于几乎所有代码的合理标准,并且它们是由制作编译器的研究人员非常慷慨地提供的。

              【讨论】:

              • 他们帮助维护的另一种方式是当您查看其他人的代码并且无法判断副作用是否是故意的时。开启警告后,您就知道他们至少意识到了这个问题。
              • 或者在我的例子中,您从嵌入式系统导入一个文件,该文件包含一个超过 3000 行 switch 语句的枚举,其中包含数千个值。 “失败”警告(通过使用 goto 避免)掩盖了一些“未处理”的错误......嵌入式编译器没有发出其中任何一个,但这些错误仍然很重要。
              【解决方案11】:

              非固定警告迟早会导致您的代码出错


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

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

              修复警告导致修复问题...经典!

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

              #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 Epicurus 教授曾经说过的那样:

              分段错误

              为了在现实世界的场景中进行调试,人们将从导致分段错误的行开始,并尝试追踪原因的根源......他们必须搜索 @ 发生了什么987654328@ 和 str 在那大量的代码中......

              直到有一天,他们发现idx 未初始化使用,因此它具有垃圾值,这导致索引字符串(方式)超出其范围,从而导致分段错误。

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

              【讨论】:

              • 你的标题:不一定。例如,建议在真正不需要括号的公式中使用括号的警告指向一个永远不会导致错误的非问题。给定编程语言中的运算符优先级不会改变。永远。
              • @MarcvanLeeuwen 你引用的实例 can 会变成错误,例如,如果不记得运算符优先级的程序员正确地修改了公式。警告告诉您:“某些人可能不清楚,请添加一些括号以使其更清楚”。尽管必须同意原始帖子的标题并不总是正确的。
              • ^ 任何事情都可能变成错误。在部分括号内的代码中引入 bug 与在全括号内的代码中一样容易。
              • 如果你遇到了分段错误,你很幸运。如果您不太幸运,您可能碰巧有 idx 恰好是您在测试中预期的值(如果预期值为 0,则不太可能),并且实际上恰好指向一些不应该打印的敏感数据部署时。
              • “IP Epicurus Professor”中的“IP”是什么? The closest知识产权,但这不符合上下文。 “哲学”的“P”? “P”代表“program”还是“programming”“互联网编程”IoT编程”?你是说 PI (principal investigator) 吗?还是别的什么?
              【解决方案12】:

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

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

              【讨论】:

                【解决方案13】:

                将警告视为错误只是一种自律方式:您正在编译一个程序来测试该闪亮的新功能,但您不能修复马虎的部分。 -Werror 没有提供其他信息。它只是非常清楚地设定了优先事项:

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

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

                没有工具是万无一失的。如果你写const float pi = 3.14;,大多数工具不会告诉你你定义的 π 精度很差,这可能会导致问题。大多数工具不会在if(tmp &lt; 42) 上引起注意,即使众所周知,给变量取无意义的名称和使用幻数是大型项目中的灾难。 必须了解,您编写的任何“快速测试”代码都只是:一个测试,您必须在继续执行其他任务之前正确完成它,同时您仍然可以看到它的缺点。如果您保持该代码不变,那么在您花费两个月的时间添加新功能后对其进行调试将变得更加困难。

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

                【讨论】:

                • 无论好坏,Rust 的clippy linting 工具实际上都会警告常量“3.14”。它实际上是一个example in the docs。但正如您可能从名称中猜到的那样,clippy 以积极提供帮助而自豪。
                • @emk 感谢这个例子,也许我应该以 从不说“从不” 的方式重新表述我的答案。我并不是说检查不精确的 π 值是不可能的,只是消除警告并不能保证良好的代码质量。
                • 错误警告给您的一件事是自动构建将失败,从而提醒您出现问题。自动化构建还允许自动化 linting(explosion 为 3...2...1.. :)
                【解决方案14】:

                众所周知,C 是一种相当低级的语言,如HLLs go。尽管 C++ 似乎是一种比 C 高级得多的语言,但它仍然具有许多相同的特征。其中一个特点是,这些语言是由程序员设计的,是为程序员设计的——特别是那些知道自己在做什么的程序员。

                (对于这个答案的其余部分,我将专注于 C。我要说的大部分内容也适用于 C++,尽管可能没有那么强烈。尽管正如 Bjarne Stroustrup 所说的那样," C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off."。)

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

                但是在 C 和 C++ 中,您可以做的很多事情都是“坏主意”,但在形式上并不是“违反规则”。有时它们在某些时候是个坏主意(但在其他时候可能是可以辩护的);有时它们几乎一直都是个坏主意。但是传统一直警告这些事情 - 因为,再次假设程序员知道他们在做什么,他们不会在没有充分理由的情况下做这些事情,并且他们会被一堆不必要的警告惹恼。

                当然,并非所有程序员真的都知道他们在做什么。尤其是,每个 C 程序员(无论多么有经验)都会经历一个成为 C 初学者的阶段。即使是经验丰富的 C 程序员也可能粗心大意并犯错误。

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

                事实证明,在大多数情况下,警告毕竟是个好主意。即使是经验丰富的程序员也已经学会(实际上,“尤其是经验丰富的程序员已经学会了”),总的来说,警告往往利大于弊。每次你故意做错事而警告令人讨厌,你可能至少有十次不小心做错事,警告让你免于进一步的麻烦。当你真的想做“错误”的事情时,大多数警告都可以被禁用或解决。

                (这种“错误”的典型例子是测试if(a = b)。大多数情况下,这是一个错误,所以现在大多数编译器都会警告它——有些甚至默认情况下。但是如果你 真的 想要将b 分配给a 并测试结果,您可以通过键入if((a = b)) 来禁用警告。)

                第二个问题是,为什么要让编译器将警告视为错误?我会说这是因为人性,特别是说“哦,这只是一个警告,这不是那么重要,我稍后会清理它”的非常容易的反应。但是如果你是一个拖延者(我不了解你,但我是一个可怕的拖延者),基本上永远推迟必要的清理工作很容易——如果你进入忽略警告的习惯,在您忽略的所有警告消息中,您会越来越容易错过重要警告消息,而这些警告消息却被忽略了。

                因此,要求编译器将警告视为错误是一个小技巧,您可以自己玩来绕过这个人类的弱点。

                就个人而言,我并不坚持将警告视为错误。 (事实上​​,如果我说实话,我可以说我几乎从未在我的“个人”编程中启用该选项。)但你可以确定我已经在工作中启用了该选项,我们的风格指南(我写)强制其使用。我会说——我怀疑大多数专业程序员会说——任何不将警告视为 C 语言错误的商店都是不负责任的行为,没有遵循公认的行业最佳实践。

                【讨论】:

                • “知道自己在做什么的程序员”——哈哈;如果我见过一个“没有真正的苏格兰人”的谬论:)
                • @Dancrumb LOL 回来了。我不太确定我是否理解No true Scotsman 谬误,但我喜欢它,所以这对我来说是一个很好的练习。我猜这里的应用程序是这样的:“没有 C 程序员会写 if(a = b),因此我们不需要警告它。” (然后有人列出了 10 个已发布产品中由该特定错误导致的 10 个严重错误。)“好吧,没有有经验的 C 程序员会写出这样的......”
                • @SteveSummit 但是一个真正经验丰富的 C 程序员可能会写 if (returnCodeFromFoo = foo(bar)) 并表示它,在一个地方捕获和测试代码(假设 only i> foo 的目的是产生副作用!)真正经验丰富的程序员可能知道这不是一种好的编码风格这一事实是不切实际的;)
                • 问题是,大多数非常有经验的程序员都会启用大多数(如果不是全部)警告。如果他们确实想使用if (returnCodeFromFoo = foo(bar)) 之类的东西,那么他们会添加注释并关闭警告(这样当维护程序员在 4 年后查看它时,他/她会意识到代码是故意的。也就是说,我与(在 Microsoft C++ 领域)坚持将 /Wall 与将警告视为错误相结合的人一起工作。嗯,它不是(除非你想放入很多抑制 cmets)。
                • 作为一个每天编写代码的人,但是当我这样做它往往是裸机(通常在我自己的设计)我发现警告非常宝贵。当将值填充到内部寄存器中时(例如 DMA 描述符位置),关于转换为指针的警告意味着我进行强制转换以清除警告。这不会是一个错误,但如果其他人(甚至我自己!)在几个月内拿起该代码,那很可能会造成混乱。此外,我还将无警告出现规则应用于我的 CAD 工具的输出。
                【解决方案15】:

                警告包含一些最熟练的 C++ 开发人员可以融入应用程序的最佳建议。它们值得保留。

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

                关闭或忽略警告就像选择忽略那些比您更熟练的人的免费建议。这是一个傲慢的教训,当你fly too close to the sun and your wings melt 或发生内存损坏错误时结束。两者之间,我随时都会从天上掉下来!

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

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

                【讨论】:

                • C++ 图灵完备与此有关。许多语言正在完善,如果你做错了什么,请不要相信你....
                • @KamiKaze 每种语言都会有惯用错误(例如,Java 无法阻止您编写不一致的 equals / hashCode),这是报告的实施质量问题。
                • @KamiKaze 图灵完整性位的出现表明在某些情况下编译器无法证明您的代码将无法按计划工作。这很重要,因为编译器不能使所有“错误”代码都成为错误。错误只能保留给语言设计者确信永远是“错误”的行为。 (通常是因为它会导致不一致的路径)。
                • 这也指出了“所有警告都是错误”的挑战。从设计上讲,警告更具机会主义性,触发一些可能正确的代码以换取更频繁地触发错误代码。错误警告会导致您无法使用完整的语言功能。
                • 然而,一般来说,程序员想要一种不仅仅做“安全”事情的语言。我们想要一种语言,它可以做我们认为我们告诉它做的事情。因此,警告仍然很重要,因为我们希望计算机做的事情的实际类别是语义类别。编译器可以通过定义“不正确”或“不安全”来解决它,但最终你仍然拥有程序员希望程序执行的行为的超类。警告有助于缩小超类的范围。
                【解决方案16】:

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

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

                【讨论】:

                  【解决方案17】:

                  为什么要启用警告?

                  C 和 C++ 编译器在报告一些常见的程序员错误方面出了名的糟糕默认情况下,例如:

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

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

                  如何启用警告?

                  这取决于你的编译器。

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

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

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

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

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

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

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

                  如何将警告视为错误?

                  这再次通过编译器开关完成。 /WX 用于 Microsoft,大多数其他人使用 -Werror。无论哪种情况,如果产生任何警告,编译都会失败。

                  【讨论】:

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

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

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

                  【讨论】:

                  • 具有讽刺意味的是,许多未定义的行为实际上并不会引起警告,而是默默地编译成一个讨厌的小定时炸弹。 ;P
                  • 问题在于,如果标准要求错误消息,则必须在出现问题的所有种情况下发出该错误消息,但如果没有出现问题,则永远不会发出该错误消息。但在诸如未定义行为之类的情况下,这可能无法决定。例如,考虑下面的代码:int i; if (fun1()) i=2; if (fun2()) i=3; char s="abcde"[i]; 当且仅当fun1()fun2() 都可以在同一函数执行中返回false 时,此代码才表现出未定义的行为。哪个可能是真的,也可能不是,但是编译器如何判断?
                  • 虽然 UB 不是没有问题,但在某种意义上它仍然是一个功能。它允许编译器进行原本无法进行的优化。每次访问数组时,Java 代码都需要执行边界检查,从而导致代码变慢。但是,我确实同意应该重新指定大部分 UB 以强制崩溃。
                  【解决方案19】:

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

                  【讨论】:

                    【解决方案20】:

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

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

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多