n. *_* m. 292 c c++ warnings c++-faq compiler-warnings
我经常听到,在编译C和C ++程序时,我应该“始终启用编译器警告”。为什么这是必要的?我怎么做?
有时我也听说我应该“将警告视为错误”。我是不是该?我怎么做?
n. *_* m. 334
C和C ++编译器在报告一些常见的程序员的失误出了名的坏默认情况下,如:
return函数中的值printf和scanf系列中的参数与格式字符串不匹配这些可以被检测和报告,通常不是默认情况下;必须通过编译器选项显式请求此功能。
这取决于您的编译器。
微软的C和C ++编译器理解开关一样/W1,/W2,/W3,/W4和/Wall。至少使用/W3。/W4并/Wall可能对系统头文件发出虚假警告,但是如果您的项目使用这些选项之一进行了正确的编译,则应这样做。这些选项是互斥的。
大多数其他编译器理解像选项-Wall,-Wpedantic和-Wextra。-Wall是必不可少的,所有其他建议都被推荐(请注意,尽管名称如此,但-Wall仅启用最重要的警告,而不是全部警告)。这些选项可以单独使用,也可以一起使用。
您的IDE可能有一种从用户界面启用这些功能的方法。
编译器警告表示您的代码中可能存在严重的问题。上面列出的问题几乎总是致命的。其他可能会或可能不会,但是您希望编译失败,即使事实证明这是一个错误的警报。调查每个警告,找到根本原因,然后加以解决。如果出现错误警报,请解决它-即,使用其他语言功能或结构,以便不再触发警告。如果事实证明这很难,请逐个禁用该特定警告。
您不希望仅将警告作为警告,即使它们都是错误警报。对于发出警告的总数少于7个的非常小的项目,这可能是可以的。不要这样 只要使您的所有项目都能干净地编译即可。
请注意,这适用于程序开发。如果您以源代码形式向世界发布项目,那么最好不要-Werror在已发布的构建脚本中提供或提供等效的版本。人们可能会尝试使用不同版本的编译器或完全不同的编译器来构建您的项目,这可能会启用一组不同的警告。您可能希望他们的建造成功。始终启用警告仍然是一个好主意,以便看到警告消息的人可以向您发送错误报告或补丁。
再次使用编译器开关完成此操作。/WX适用于Microsoft,大多数其他人使用-Werror。无论哪种情况,如果产生任何警告,编译都将失败。
Ste*_*mit 91
众所周知,随着HLL的发展,C是一种相当底层的语言。尽管C ++似乎比C语言要高级得多,但它仍然具有许多特质。这些特征之一就是这些语言是由程序员设计的,是为程序员而设计的,尤其是那些知道自己在做什么的程序员。
[对于此答案的其余部分,我将重点介绍C。我会说的大多数内容也适用于C ++,尽管可能不那么适用。尽管正如Bjarne Stroustrup所说的那样,“ C使脚上的射击变得容易; C ++使其更难,但是当您这样做时,它会使您的整个腿发疯。” ]
如果您知道自己在做什么- 真的知道自己在做什么-有时您可能必须“违反规则”。但是大多数时候,我们大多数人都会同意,良好的规则会让我们所有人摆脱困境,并且大胆地始终违反这些规则是一个坏主意。
但是在C和C ++中,您可以做很多令人惊讶的事情,这些都是“坏主意”,但并不是形式上的“违规”。有时在某些情况下,这是个坏主意(但在其他时候可能是有道理的);有时,几乎所有时间他们都是一个坏主意。但是传统上一直不对这些事情发出警告-因为再次假设,程序员知道自己在做什么,没有正当的理由他们就不会做这些事情,他们会被一堆烦恼不必要的警告。
但是当然,并不是所有的程序员都真正知道自己在做什么。尤其是,每个C程序员(无论经验如何)都经历了成为新手C程序员的阶段。甚至有经验的C程序员也会变得粗心和犯错。
最后,经验表明,不仅程序员确实会犯错误,而且这些错误会带来真正的严重后果。如果您犯了一个错误,并且编译器没有警告您,并且由于某种原因程序不会立即崩溃或执行明显错误的操作,那么该错误可能会在那里潜伏,隐藏,有时长达数年,直到导致一个真正的大问题。
因此,事实证明,在大多数情况下,警告毕竟是个好主意。即便是有经验的程序员也已经学会(实际上,“ 特别是有经验的程序员已经学会”),总的来说,警告往往弊大于利。每次您故意做错了事而警告是令人讨厌的事,您至少有十次无意中做了错事,而警告使您免于进一步的麻烦。当您确实想做“错误”的事情时,大多数警告可以禁用或解决几次。
(这样的“错误”的一个典型的例子是测试if(a = b)在大多数情况下,这是一个错误,这几天警告它,大多数编译器- 。有些甚至默认但是,如果你真的想同时分配b到a和试验结果,您可以通过键入来禁用警告if((a = b))。)
第二个问题是,为什么要让编译器将警告视为错误?我会说这是由于人的天性,特别是说“哦,那只是一个警告,并不是那么重要,我稍后再整理”的反应太简单了。但是,如果您是拖延症患者(我不知道您是什么,但我是一个糟糕的拖延症患者),则很容易将清除工作推迟到根本,而且-如果您养成了无视警告的习惯,在您忽略的所有消息之中,越来越容易错过坐在那里的重要警告消息,却未被注意。
因此,要求编译器将警告视为错误,这是您可以解决这个人为错误的小技巧。
就我个人而言,我不太坚持将警告视为错误。(实际上,老实说,我可以说我实际上从未在“个人”编程中启用该选项。)但是,您可以确定我在工作时启用了该选项,在我们的样式指南中(写道)要求其使用。我想说-我怀疑大多数专业的程序员都会说-任何不将警告视为C错误的车间都是不负责任的行为,没有遵循公认的行业最佳实践。
Cor*_*ica 37
警告包括一些最熟练的C ++开发人员可以融入应用程序的最佳建议。他们值得保持。
C ++是一种图灵完整的语言,在很多情况下,编译器必须简单地相信您知道自己在做什么。但是,在许多情况下,编译器可以意识到您可能不打算编写自己编写的内容。一个典型的例子是与参数不匹配的printf()代码,或传递给printf的std :: strings(这对我而言从来没有!)。在这些情况下,您编写的代码不是错误。这是一个有效的C ++表达式,带有可让编译器执行的有效解释。但是编译器有很强的直觉,您只是忽略了现代编译器易于检测的内容。这些是警告。对于编译器而言,它们是显而易见的事情,因为您可能会使用C ++的所有严格规则,而这些规则可能已经被您忽略了。
关闭或忽略警告就像选择忽略那些比您熟练的人提供的免费建议。这是在胡贝里斯上的一课,当您太靠近太阳飞行并且翅膀融化或发生内存损坏错误时,该课程就会结束。在这两者之间,我每天都会从天上掉下来!
“将警告视为错误”是这种理念的极端版本。这里的想法是,您解决编译器给您的每条警告-您听取每条免费建议,并采取相应措施。对于您来说,这是否是一个好的开发模型取决于团队和正在开发哪种产品。这是和尚可能会采取的禁欲法。对于某些人来说,它很棒。对于其他人则不是。
在我的许多应用程序中,我们都不将警告视为错误。我们这样做是因为这些特定的应用程序需要在具有不同年龄的多个编译器的多个平台上进行编译。有时,我们发现实际上不可能在不将警告变成另一平台的警告的情况下将警告固定在一侧。所以我们只是小心。我们尊重警告,但我们不会向后弯腰。
小智 18
处理警告不仅使代码更好,而且使您成为更好的程序员。警告会告诉您今天对您来说似乎很少的事情,但是有一天,这种坏习惯会再次出现并咬住您的头。
使用正确的类型,返回该值,评估该返回值。花时间思考“在这种情况下,这真的是正确的类型吗?” “我需要退货吗?” 还有大个子 “此代码在未来10年内是否可以移植?”
首先养成编写无警告代码的习惯。
Jos*_*iah 16
其他答案很好,我不想重复他们所说的话。
尚未正确提及的“为什么启用警告”的另一方面是,它们在代码维护方面有很大帮助。当您编写一个规模很大的程序时,就不可能一次把整个事情都牢记在心。通常,您有一个正在积极编写和考虑的功能,或者可能在屏幕上可以引用的一个文件或三个功能,但是该程序的大部分存在于后台的某个地方,因此您必须相信它继续工作。
发出警告,并使其充满活力,并尽可能多地出现在您的脸上,可以帮助您警告您是否更改了某些内容而导致看不到的麻烦。
以clang警告为例-Wswitch-enum。如果您在枚举上使用开关并且错过了可能的枚举值之一,则会触发警告。您可能会认为这是一个不太可能犯的错误:您可能至少在编写switch语句时查看了枚举值列表。您甚至可能拥有一个为您生成切换选项的IDE,不留任何人为错误的余地。
当六个月后,您向枚举添加另一个可能的条目时,此警告便真正产生了。同样,如果您正在考虑所涉及的代码,则可能会很好。但是,如果此枚举用于多种不同目的,并且是您需要额外选择的目的之一,则很容易忘记更新6个月未触及的文件中的开关。
您可以以与自动化测试用例相同的方式来考虑警告:它们可以帮助您确保代码合理并在您首次编写代码时就做您需要的事情,但是它们可以帮助您确保对代码的理解。在生产时不断做您需要的事情。区别在于,测试用例仅适用于您的代码要求,而您必须编写它们,而警告则几乎适用于几乎所有代码的明智标准,并且警告由编写编译器的boffins慷慨地提供。
gsa*_*ras 13
例如,调试分段故障需要程序员跟踪故障的根源(原因),该故障根源通常位于代码中比最终导致分段故障的行更靠前的位置。
非常典型的原因是编译器发出了您忽略的警告的行,而导致分段错误的行最终导致了错误的行。
解决警告会解决问题。.经典!
上面的演示。考虑以下代码:
#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;
}
Run Code Online (Sandbox Code Playgroud)
当使用传递给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]);
| ^
Run Code Online (Sandbox Code Playgroud)
我仍然可以忽略并执行代码。.然后,我将见证一个“巨大的”分段错误,就像我的IP Epicurus教授曾经说过的那样:
分段故障
为了在现实世界中进行调试,我们将从导致细分错误的那一行开始,并尝试追踪原因的根源。他们将必须寻找在这巨大数量之内i和之str内发生的事情。那里的代码...
直到有一天,他们发现自己处于idx未初始化使用状态,因此它具有垃圾值,这导致对字符串(方式)的索引超出其范围,从而导致分段错误。
如果他们没有忽略警告,他们将立即发现该错误!
Dmi*_*yev 12
将警告视为错误只是自律的一种手段:您正在编译一个程序来测试该闪亮的新功能,但是直到修复了草率的零件之后,您才能这样做。没有Werror提供其他信息,它只是非常明确地设置了优先级:
在解决现有代码中的问题之前,请勿添加新代码
真正重要的是心态,而不是工具。编译器诊断输出是一个工具。MISRA(用于嵌入式C)是另一种工具。使用哪一个都没有关系,但是可以说编译器警告是您可以获得的最简单的工具(只需设置一个标志),并且信噪比非常高。因此,没有理由不使用它。
没有工具是绝对可靠的。如果您写的话const float pi = 3.14;,大多数工具不会告诉您您定义了吗?精度低下可能会导致问题。if(tmp < 42)即使众所周知,给变量赋予无意义的名称并使用幻数是在大型项目中造成灾难的一种方式,大多数工具也不会引起人们的注意。您必须了解,编写的任何“快速测试”代码都只是这样:一个测试,在继续执行其他任务之前必须正确进行测试,同时仍然要看到它的缺点。如果您按原样保留这些代码,那么在花费两个月的时间添加新功能后进行调试将变得非常困难。
一旦进入正确的思维模式,就没有必要使用了Werror。将警告作为警告可以使您做出明智的决定,是继续运行您将要启动的调试会话还是有意义的,还是先中止它并首先修复警告。
作为使用旧式嵌入式C代码的人,启用编译器警告有助于显示很多弱点和提出修复建议时需要研究的领域。在GCC利用-Wall和-Wextra,甚至-Wshadow已经成为非常重要的。我不会一一列举所有危害,但会列出一些弹出的提示,以帮助显示代码问题。
这很容易指出未完成的工作和可能没有利用所有传递变量的领域。让我们看一个可能触发此操作的简单函数:
int foo(int a, int b)
{
int c = 0;
if (a > 0)
{
return a;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
仅在不使用-Wall或-Wextra的情况下进行编译不会返回任何问题。-Wall会告诉您尽管c从未使用过:
foo.c:在函数“ foo”中:
foo.c:9:20:警告:未使用的变量'c'[-Wunused-variable]
-Wextra还将告诉您参数b不执行任何操作:
foo.c:在函数“ foo”中:
foo.c:9:20:警告:未使用的变量'c'[-Wunused-variable]
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;
}
Run Code Online (Sandbox Code Playgroud)
启用-Wshadow时,很容易发现此问题。
foo.c:11:9:警告:'c'声明掩盖了全局声明[-Wshadow]
foo.c:1:5:注意:此处有阴影的声明
这在gcc中不需要任何额外的标志,但是在过去,它仍然是问题的根源。尝试打印数据但有格式错误的简单函数如下所示:
void foo(const char * str)
{
printf("str = %d\n", str);
}
Run Code Online (Sandbox Code Playgroud)
由于格式标记错误,因此无法打印字符串,gcc会很高兴地告诉您这可能不是您想要的:
foo.c:在函数“ foo”中:
foo.c:10:12:警告:格式'%d'期望类型为'int'的参数,但是参数2的类型为'const char *'[-Wformat =]
这些只是编译器可以为您仔细检查的许多事情中的三件事。还有许多其他方法,例如使用其他人指出的未初始化变量。
小智 7
编译器警告是你的朋友(不是喊叫,大写强调)。
我在旧版 Fortran-77 系统上工作。编译器告诉我有价值的事情:子例程调用中的参数数据类型不匹配,在将值设置到变量之前使用局部变量,如果我有一个未使用的变量或子例程参数。这些几乎总是错误。
避免长篇幅:当我的代码干净利落地编译时,97% 可以正常工作。与我一起工作的另一个人在编译时关闭所有警告,在调试器中花费数小时或数天,然后要求我提供帮助。我只是用警告编译他的代码,然后告诉他要修复什么。
您应该始终启用编译器警告,因为编译器通常会告诉您代码有什么问题。为此,您需要传递-Wall -Wextra给编译器。
您通常应该将警告视为错误,因为警告通常表示您的代码有问题。但是,通常很容易忽略这些错误。因此,将它们视为错误会导致构建失败,因此您不能忽略这些错误。要将警告视为错误,请传递-Werror给编译器。
这是对C的一个具体答案,为什么这对C比对其他任何东西都重要。
#include <stdio.h>
int main()
{
FILE *fp = "some string";
}
Run Code Online (Sandbox Code Playgroud)
此代码编译时带有警告。C 语言中的警告几乎是地球上所有其他语言中的(应该是)错误(汇编语言除外)。C 语言中的警告几乎总是伪装成错误。警告应固定,而不是禁止显示。
与gcc,我们这样做gcc -Wall -Werror。
这也是某些MS非安全API警告的高度保证的原因。大多数使用C语言进行编程的人都已经学会了将警告视为错误的艰难方法,而这些东西似乎不是同一种东西,并且需要不可移植的修复程序。
我曾经在一家制造电子测试设备的大型(财富 50 强)公司工作。
我小组的核心产品是一个 MFC 程序,多年来,它产生了数以百计的警告。在几乎所有情况下都被忽略了。
当出现错误时,这是一个可怕的噩梦。
在那个职位之后,我有幸被聘为一家新创业公司的第一位开发人员。
我鼓励所有构建都采用“无警告”策略,编译器警告级别设置为非常嘈杂。
我们的做法是使用#pragma warning - push/disable/pop 用于开发人员确定非常好的代码,以及调试级别的日志语句,以防万一。
这种做法对我们很有效。
由于某些原因,C++ 中的编译器警告非常有用。
1 - 它允许向您展示您可能犯的错误,这些错误会影响您操作的最终结果。例如,如果你没有初始化一个变量,或者如果你把“=”而不是“==”(只有例子)
2 - 它还允许向您展示您的代码不符合 C++ 标准的地方。这很有用,因为如果代码符合实际标准,则很容易将代码移动到其他平台中。
通常,警告非常有用,可以向您显示代码中的哪些错误会影响算法的结果或防止用户在使用您的程序时出现错误。
小智 5
忽略警告意味着您留下了草率的代码,这不仅可能在将来给其他人带来问题,而且会使您不太注意重要的编译消息。编译器输出越多,任何人都会注意到或打扰的越少。越干净越好。这也意味着你知道你在做什么。警告是非常不专业、粗心和危险的。
| 归档时间: |
|
| 查看次数: |
26375 次 |
| 最近记录: |