Answers:
众所周知,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
。无论哪种情况,如果产生任何警告,编译都会失败。
众所周知,随着HLL的发展,C是一种相当底层的语言。尽管C ++似乎比C语言要高级得多,但它仍然具有许多特质。这些特征之一是这些语言是由程序员设计的,是为程序员而设计的,尤其是为知道自己在做什么的程序员设计的。
[在本答案的其余部分中,我将重点介绍C。我会说的大多数内容也适用于C ++,尽管可能不那么适用。尽管正如Bjarne Stroustrup所说的那样,“ C使脚上的射击变得容易; C ++使其更难,但是当您这样做时,它会使您的整个腿发疯。” ]
如果您知道自己在做什么- 真的知道自己在做什么-有时您可能不得不“违反规则”。但是大多数时候,我们大多数人都会同意,良好的规则会让我们所有人摆脱困境,并且大胆地始终违反这些规则是一个坏主意。
但是在C和C ++中,您可以做很多令人惊讶的事情,这些都是“坏主意”,但并不是形式上的“违背规则”。有时在某些情况下,这是个坏主意(但在其他时候可能是有道理的);有时,几乎所有时间他们都是一个坏主意。但是传统上一直不对这些事情发出警告-因为,再次假设,程序员知道他们在做什么,没有正当的理由他们就不会做这些事情,他们会被一堆烦恼不必要的警告。
但是当然,并不是所有的程序员都真正知道他们在做什么。尤其是,每个C程序员(无论经验如何)都经历了成为新手C程序员的阶段。甚至有经验的C程序员也会变得粗心和犯错。
最后,经验表明,不仅程序员确实会犯错误,而且这些错误可能会带来真正的严重后果。如果您犯了一个错误,并且编译器没有警告您,并且由于某种原因程序不会立即崩溃或执行明显错误的操作,那么该错误可能会在那里潜伏,隐藏,有时长达数年,直到导致一个真正的大问题。
因此,事实证明,在大多数情况下,警告毕竟是个好主意。即便是有经验的程序员也已经学会(实际上,“ 尤其是有经验的程序员已经学会”),总的来说,警告往往弊大于利。每次您故意做错了事而警告是令人讨厌的事,您至少有十次无意中做了错事,而警告使您免于进一步的麻烦。当您确实想做“错误”的事情时,大多数警告可以禁用或解决几次。
(这样的“错误”的一个典型的例子是测试if(a = b)
在大多数情况下,这是一个错误,这几天警告它,大多数编译器- 。有些甚至默认但是,如果你真的想同时分配b
到a
和试验结果,您可以通过键入来禁用警告if((a = b))
。)
第二个问题是,为什么要让编译器将警告视为错误?我会说这是由于人的天性,特别是说“哦,那只是一个警告,不是那么重要,我稍后再整理”的反应太简单了。但是,如果您是拖延症患者(我不知道您是什么,但我是一个糟糕的拖延症患者),则很容易将必要的清理工作推迟到根本上-如果您养成了无视警告的习惯,在您忽略的所有警告消息中,越来越容易错过坐在那里的重要警告消息,而未被注意。
因此,要求编译器将警告视为错误,这是您可以解决这个人为错误的小技巧。
就我个人而言,我不太坚持将警告视为错误。(实际上,老实说,我可以说我实际上从未在“个人”编程中启用该选项。)但是,您可以确定我在工作时启用了该选项,在我们的样式指南中写道)要求其使用。我想说-我怀疑大多数专业的程序员都会说-任何不将警告视为C错误的车间都是不负责任的行为,没有遵循公认的行业最佳实践。
if(a = b)
,因此我们不需要警告它。” (然后某人会列出由该特定错误导致的10个发行产品中的10个严重错误的列表。)“好吧,没有经验丰富的 C程序员会写那个……”
if (returnCodeFromFoo = foo(bar))
和意思,以捕捉和在一个地方测试代码(假设唯一的目的foo
就是有副作用!),一个事实真的有经验的程序员可以知道这是不是一个好的编码风格
if (returnCodeFromFoo = foo(bar))
,则可以添加注释并关闭警告(以便维护程序员在4年后查看该警告时,他/她将意识到该代码是故意的。也就是说,我工作了与(在Microsoft C ++领域中)坚持使用/ Wall并把警告视为错误的人相结合才是解决之道,嗯,不是(除非您想发表很多禁止意见)
警告包含一些最熟练的C ++开发人员可以融入应用程序的最佳建议。他们值得保持。
C ++是一种图灵完整的语言,在很多情况下,编译器必须简单地相信您知道自己在做什么。但是,在许多情况下,编译器可以意识到您可能不打算编写自己编写的内容。一个典型的例子是与参数不匹配的printf()代码,或者传递给printf的std :: strings(这对我来说从来没有!)。在这些情况下,您编写的代码不是错误。这是一个有效的C ++表达式,带有可让编译器执行的有效解释。但是编译器有很强的直觉,您只是忽略了现代编译器易于检测的内容。这些是警告。对于编译器而言,它们是显而易见的事情,您可能会忽略使用C ++的所有严格规则。
关闭或忽略警告就像选择忽略那些比您熟练的人提供的免费建议。这是在huberis中的一课,当您飞得太靠近太阳并且机翼融化或发生内存损坏错误时,该课程就会结束。在这两者之间,我每天都会从天上掉下来!
“将警告视为错误”是这种理念的极端版本。这里的想法是,您解决编译器给您的每条警告-您听取每条免费建议,并采取相应措施。对于您来说,这是否是一个好的开发模型取决于团队和您正在开发哪种产品。这是和尚可能会采取的禁欲态度。对于某些人来说,它的效果很好。对于其他人则不是。
在我的许多应用程序中,我们都不将警告视为错误。我们这样做是因为这些特定的应用程序需要在具有不同年龄的多个编译器的多个平台上进行编译。有时,我们发现实际上不可能在不将警告转变为另一平台上的警告的情况下将警告固定在一侧。所以我们只是小心。我们尊重警告,但我们不会向后弯腰。
equals
/ hashCode
),并且报告了其中的一种实现质量。
其他答案非常好,我不想重复他们所说的话。
尚未正确提及的“为什么启用警告”的另一方面是,它们对代码维护有很大帮助。当您编写一个很大的程序时,就不可能一次把整个事情都牢记在心。通常,您有一个正在积极编写和考虑的功能,或者一个可能在屏幕上引用的文件或三个功能,但是该程序的大部分存在于后台的某个地方,因此您必须相信它继续工作。
发出警告,并使其充满活力,并尽可能多地出现在您的脸上,有助于警告您是否更改了某些内容而导致看不到的麻烦。
以clang警告为例-Wswitch-enum
。如果您在枚举上使用开关并且错过了可能的枚举值之一,则会触发警告。您可能会认为这是一个不太可能犯的错误:您可能至少在编写switch语句时查看了枚举值列表。您甚至可能拥有一个为您生成切换选项的IDE,不留任何人为错误的余地。
六个月后,当您向枚举添加另一个可能的条目时,此警告真正产生了。同样,如果您正在考虑所涉及的代码,则可能会很好。但是,如果此枚举用于多种不同目的,并且是您需要额外选择的目的之一,那么很容易忘记更新6个月未触及的文件中的开关。
您可以以与自动化测试用例相同的方式来考虑警告:它们可以帮助您确保代码合理,并在您首次编写代码时就做您需要的事情,但是它们可以帮助您确保对代码的理解。在生产时不断做您需要的事情。区别在于,测试用例仅适用于您的代码要求,而您必须编写它们,而警告则几乎适用于几乎所有代码的明智标准,并且警告由编写编译器的boffins慷慨地提供。
例如,调试分段故障需要程序员跟踪故障的根源(原因),该故障根源通常位于代码中比最终导致分段故障的行更靠前的位置。
非常典型的原因是编译器发出了您忽略的警告的行,而导致分段错误的行是最终引发错误的行。
解决警告会解决问题。.经典!
上面的演示。考虑以下代码:
#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教授曾经说过的那样:
分段故障
为了在真实的场景中调试这个,一会从引起分段错误行开始,并试图追查是什么原因的根源。他们将不得不寻找发生了什么事i
,并str
认为巨大的金额内那里的代码...
直到有一天,他们发现自己处于idx
未初始化使用的情况,因此它具有垃圾值,这导致对字符串(方式)的索引超出其范围,从而导致分段错误。
如果只有他们没有忽略警告,他们将立即发现该错误!
str[idx]
” 是不是 “好了,这里是str
和idx
定义`?
idx
碰巧是您在测试中期望的值(如果期望值为0,则不太可能),并且实际上碰巧指向了一些敏感数据,这些数据在部署时永远不应打印。
将警告视为错误只是自律的一种手段:您正在编译一个程序来测试该闪亮的新功能,但是直到修复了草率的零件之后,您才能这样做。没有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;
}
仅在不使用-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;
}
启用-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
”警告。我发现很难理解有多少“程序员”会忽略这些错误(实际上,我不确定自己为什么不是错误)
sizeof
是无符号的,但是默认的整数类型是有符号的。的sizeof
结果类型,size_t
被典型地用于任何有关类型的尺寸,诸如,例如,对准或阵列/容器元素计数,而在一般的整数旨在被用作“ int
除非另有规定”。因此,考虑到有多少人被教导要使用int
其容器进行迭代(与相比),将其int
设为size_t
错误会破坏一切。; P
这是对C的特定答案,为什么这对C比对其他任何东西都重要。
#include <stdio.h>
int main()
{
FILE *fp = "some string";
}
此代码编译时带有警告。C 语言中的警告几乎是地球上所有其他语言中的(应该是)错误(除非汇编语言)。C 语言中的警告几乎总是伪装成错误。警告应固定,而不是禁止显示。
与gcc
,我们这样做gcc -Wall -Werror
。
这也是某些MS非安全API警告的高度保证的原因。大多数使用C语言进行编程的人都已经学会了将警告视为错误的艰难方法,而这些东西似乎不是同一种东西,并且需要不可移植的修复程序。
我曾经在一家生产电子测试设备的大型公司(《财富》 50强)工作。
我小组的核心产品是MFC程序,这些程序多年来产生了数以百计的警告。几乎在所有情况下都忽略了这些内容。
发生错误时,这简直是噩梦般的噩梦。
担任该职位后,我很幸运被聘为一家新公司的第一位开发人员。
我鼓励所有版本都采用“不警告”政策,并且将编译器警告级别设置为非常嘈杂。
我们的做法是使用#pragma warning-对于开发人员确定确实不错的代码使用push / disable / pop,并在调试级别使用log语句,以防万一。
这种做法对我们来说效果很好。
#pragma warning
不仅抑制警告,它还具有双重目的,即可以与其他程序员快速交流某些有意而非偶然的信息,并充当搜索标签,以便在某些内容出现故障时快速查找可能有问题的区域,但不能纠正错误/警告。修理它。
将警告视为错误只有一个问题:当您使用来自其他来源的代码(例如,micro $ ** t库,开放源代码项目)时,它们没有正确执行工作,并且编译其代码会产生大量错误警告。
我总是写我的代码,因此它不会生成任何警告或错误,并对其进行清理,直到其编译时不会产生任何外来的噪音。我必须与之打交道的垃圾使我感到震惊,当我不得不构建一个大项目并看着警告流经过编译应该只宣布它处理的文件的地方时,我感到惊讶。
我也记录了我的代码,因为我知道软件的真正生命周期成本主要来自维护,而不是最初编写它,但这是另一回事了...
-Wall
而你使用-Wall -Wextra
。
该C ++编译器接受编译代码的事实,显然会导致不确定的行为在所有在编译器的一大缺陷。他们不解决此问题的原因是因为这样做可能会破坏某些可用的版本。
大多数警告应该是致命错误,它们会阻止构建完成。仅显示错误并进行构建的默认设置是错误的,如果您不覆盖它们以将警告视为错误并留下一些警告,则可能会导致程序崩溃并随机执行操作。
int i; if (fun1()) i=2; if (fun2()) i=3; char s="abcde"[i];
当且仅当这两个代码展品不确定的行为fun1()
,并fun2()
可以返回false
上同样功能的执行。可能正确或不正确,但是编译器如何分辨?
您绝对应该启用编译器警告,因为某些编译器不善于报告一些常见的编程错误,包括以下内容:
->初始化变量被遗忘->从函数返回值被遗漏-> printf和scanf系列中的简单参数与不使用函数而无需事先声明的格式字符串不匹配,尽管仅在c中发生
因此,由于可以检测和报告这些功能,因此通常默认情况下不会;因此必须通过编译器选项明确请求此功能。
放轻松:您不必,没有必要。-Wall和-Werror是由代码重构狂为自己设计的:它是由编译器开发人员发明的,以避免在编译器在用户端更新后破坏现有的版本。该功能什么都不是,而是决定是否中断构建的全部内容。
是否使用它完全取决于您的偏好。我一直使用它,因为它可以帮助我纠正错误。
-Wall and -Werror was designed by code-refactoring maniacs for themselves.
[需要引用]
-Wall
and 的情况下进行编译,而-Werror
只是在询问是否是一个好主意。从您的最后一句话来看,听起来像是您在说。