在 GCC 中编译时使用 `-Wextra` 标志的缺点

Posted

技术标签:

【中文标题】在 GCC 中编译时使用 `-Wextra` 标志的缺点【英文标题】:Disadvantages of using the `-Wextra` flag when compiling in GCC 【发布时间】:2015-02-07 11:45:16 【问题描述】:

我知道应该始终同时使用 -Wall-Wextra 进行编译,因为它们会启用警告并帮助我们理解我们的错误(如果有)。

我读到不建议使用 -Wextra 编译器标志,因为它过于冗长,有很多误报。

读到这里我很惊讶。所以我开始搜索它,但我没有得到任何答案,因为所有搜索结果都显示“-Wextra 标志是做什么的?”。

所以,我的问题是

-Wextra 标志在哪些情况下会发出不必要的警告? 是否可以阻止 -Wextra 标志启用导致 GCC 发出此类警告的其他标志?

【问题讨论】:

你在哪里读到的? @Wintermute ,来自here 什么是“无用警告”?所有警告都有用 @EdHeal,你是对的。我已经编辑过了。 我不同意他的观点。 -Wextra中有很多有用的警告,虚假的警告很容易被压制。例如,未使用的参数可以在 C++ 中未命名,而在 C 中您可以编写 (void) varname; 以指示该变量是未使用的。始终同时使用-Wall-Wextra,这是我的建议,您不会花费数小时进行调试,因为您在if(); 之后看不到那个该死的分号。不过,我承认意见在这里发挥了作用。 【参考方案1】:

-Wextra 警告的用处在于相应的警告具有这些属性(或多或少):

    它们会产生误报。

    误报比较频繁。

    调整代码以避免误报是一件麻烦事。

结果是,许多启用-Wall -Wextra 的项目放弃了试图避免所有警告的尝试。因此,当你编译这样一个项目时,你会看到成百上千的警告,几乎都是关于合法代码的。而你应该看到的一个警告在无尽的警告流中被忽视了。更糟糕的是:正常编译会吐出如此多的警告这一事实使您对避免新代码引入的新警告感到草率。一旦一个项目达到几十个警告,战斗通常就结束了;将警告计数恢复为零需要付出巨大的努力。

这是一个被-Wextra 狂吠的完全合法的代码示例:

void myClass_destruct(MyClass* me) 

它是一个析构函数,它是空的。然而,它应该只是为了方便在适当的点(子类析构函数)调用它,以便向MyClass 添加需要清理的内容。但是,-Wextra 将在未使用的 me 参数处咆哮。这迫使程序员编写如下代码:

void myClass_destruct(MyClass* me) 
    (void)me;    //shut up the compiler barking about the unused parameter

这是普通的噪音。编写这样的代码很烦人。但是,为了在-Wextra 机制下实现零警告计数,需要将这种噪声添加到代码中。所以,你可以选择这三个中的任何两个:

    清洁码

    零警告计数

    -Wextra 警告

明智地选择你想放弃哪一个,你不会得到全部三个。

【讨论】:

当然,对一个人来说是噪音不一定是对另一个人来说是噪音。有些人可能更喜欢在代码中将所有未使用的函数参数标记为未使用,以明确表示这是故意的。 (但在这种情况下,我同意你的看法。) 请注意,(void)me; 技巧有时也不起作用。在至少一个 GCC 版本中(抱歉,不记得是哪个版本了),我让那行给了我一个“警告:无效的声明”警告。不过,我不太记得我是如何设法绕过它的,但它变得丑陋了。 您可以通过编写myClass_destruct(MyClass*)myClass_destruct(MyClass* /*me*/) 来修复未使用的参数警告。在我看来,两者都不像“普通噪音”,并且在故意不使用参数时都可以清楚地说明。 @Nemo 好主意。必须记住,下次我必须打开该警告进行开发。谢谢。 这个问题被标记为“C”,你不能只省略 C 中的函数参数名称。参见例如***.com/questions/7090998/…【参考方案2】:

一些-Wextra 警告正在强制执行适当的编码样式,这可能与某些人员流程发生冲突。这是避免它的唯一原因。在这种情况下,我的解决方案是使用 -Wno-* 语法“更正”精确设置。

例如,-Wextra 暗示 -Wempty-body,它会警告您一些控制结构的空主体,例如 ifelsewhile。但是,如果你一方面遵循“迭代”实现风格,另一方面又想要大量警告,这会让你感到不舒服。

这是不断发展的代码示例:

void doSomething()

    if (validatePreConditions())
    
         //TODO Handle it some time.
    

    // ... Here you have real code.


如果您有-Werror,并且想要在没有实施前置条件检查的情况下开始测试,那么您就有麻烦了。

但这里有个陷阱,“开发中”代码可以接受的内容是在代码投入生产时进行修复。因此,对于这种情况,我的选择是使用不同的警告集和不同的构建配置文件。因此,例如,“开发人员”构建可以有空的主体,但任何要合并到主线 MUST NOT 的东西都有这样的差距。

另一个例子是-Wignored-qualifiers,它包含在-Wextra中。在某些情况下,您知道编译器会忽略您的 const 限定符,但您添加它只是为了您自己的舒适(我个人不支持返回类型的这些限定符,但有些人认为它们在这些标记上做得很好)。

【讨论】:

当我在我的代码中留下这样的//TODO 时,它总是以assert(0 && "TODO"), abort(); 的形式出现,只是为了确保我不会忘记... 非常有趣但颇具争议的方法,因为它只会在运行时被发现。我更喜欢一些编译时间#define,这样在代码投入生产并在这样的矿井上执行之前,这将是可见的。 加一个-Wno-* set-refining 选项,这就是我一直在做的事情! 我只保留几组 -W* 并根据需要的结果在它们之间切换。【参考方案3】:

在所有情况下 -Wextra 标志都会发出 uneccessary 警告?

您对不必要的定义是什么?您可以查阅手册(这是针对4.9.2)以确切了解警告标志的作用。然后你可以自己决定它是否适合你的代码库。

是否可以阻止 -Wextra 标志启用其他标志 导致 GCC 发出这些类型的警告?

在警告前贴上-Wno- 以禁用它。并非所有警告都有警告标志,这意味着您可能必须坚持使用它。您通常还可以通过浏览他们的bugtracker 来查找更多信息,方法是搜索某些警告标志并查看是否存在任何讨论。如果它不在文档中,它可能在那里。开发人员也可能会解释为什么事情是这样的。


为了解决您在 cmets 中链接到的评论,

...或有关缺少字段初始值设定项的警告(当您 故意用 0 初始化结构,使编译器 自动零初始化其他一切)。

这个论点没有实际意义,因为它已经是fixed。例如,以下代码产生no missing initializer fields warning:

typedef struct  int a; int b;  S;

int main()

    S s =  0 ;

【讨论】:

-fdiagnostics-show-option 可用于使用Wno- 禁用标志。 结构初始化的例子是当第一个成员是聚合类型时。在这种情况下,即使是 -Wall 也只会发出警告,尽管标准不需要诊断。 @Ouah 这是错误报告中的test case。 (-Wall 不会警告我,除非你指的是别的东西。) 那么这是一个不同的问题。例如,Gcc 用-Wall(也为 0, )警告struct struct int a; x; s = 0 ;。使用 GCC 4.9.2 测试。 @remyabel 在您链接的第一个成员的测试用例中是int。即使标准不需要诊断,您也会收到与int x[1][1] = 0; 相同的警告。我要展示的是-Wextra-Wall 都在发布标准不需要的诊断(是的,标准允许它像这样表现)。【参考方案4】:

很简单;

无用的警告是永远不会指出实际错误的警告。

当然这是无法确定的,不可能事先知道某个特定警告是否会“拯救培根”。

所有 GCC 警告有时都会指出真正的错误,因此 GCC 消息的一般主题是 -Wall 警告很可能指向错误,如果没有,很容易抑制。所以这与 -Werror 配合得很好。

-Wextra 消息,OTOH 很少指出真正的错误,或者可能指出一些旧代码中常见的结构,或者很难更改为表达代码的替代方式。所以有时它们可​​能确实“过于冗长”。

对于新代码,您通常需要打开 -Wextra。

对于旧代码,如果它不是“太冗长”,则应该打开它。

【讨论】:

【参考方案5】:

警告基本上分为三类,GCC 不太擅长将它们分组或者说清楚:

    警告表明某些东西在源代码级别正式无效,甚至不应该编译,但无论出于何种原因(通常与遗留代码兼容)都允许这样做。这些是语言标准规定的。

    警告表示如果您的程序到达代码中出现警告的位置,它必然会调用未定义的行为。如果代码无法访问,您的程序可能仍然有效,实际上这很可能发生在以下情况:

    int i = 1;
    if (INT_MAX > 0x7fffffff) <<= 31;
    

    警告纯粹是启发式的,本身并不表示您的程序有任何编程错误或无效,编译器只是警告您,您的意图可能与您编写的内容不同。

-Wextra 会打开很多“类型 3”警告。

通常每个人都同意类型 1 和 2 是有价值的,但如上所述,即使类型 2 也可能有误报。类型 3 通常有很多误报;其中一些可以通过对代码进行无害的更改来抑制,而另一些则需要实际上使您的代码变得更糟(或在本地关闭警告)以避免它们。

这是人们不同意的地方。对于某些程序员,您只需打开-Wall -Wextra 并在本地解决或禁用出现误报的警告。但是,除非您这样做,否则误报会产生大量噪音并可能降低信噪比,导致您习惯于在编译时看到警告,从而不会注意到更严重的警告。

【讨论】:

【参考方案6】:

有(至少)两个 C 程序员阵营:那些认为编译器有时可能知道得更好,而那些认为编译器是谁来告诉我如何编写我的代码,毕竟,我知道我的做。

Camp 1 尽可能多地启用警告,使用 lint 和其他代码检查器甚至可以获得有关有问题或可能损坏的代码的更多提示。

Camp 2 不喜欢他们的草率总是被机器指出来,更不用说花费额外的精力来修复他们认为完美的东西了。

Camp 1 受到行业的追捧,因为它们产生的错误代码更少、缺陷更少、客户错误报告更少。如果你正在编程运载火箭、卫星设备,任何需要安全的东西,控制非常昂贵的设备,这个阵营的心态就是需要的。

Camp 2 显然太过频繁了,而您使用的许多应用程序(在您的计算机、智能手机、电器等)的总体软件质量表明了这一点。

你想加入哪个阵营? -Wextra 的缺点取决于这个答案。一个人的缺点就是另一个人的优点。

【讨论】:

以上是关于在 GCC 中编译时使用 `-Wextra` 标志的缺点的主要内容,如果未能解决你的问题,请参考以下文章

使用 -mfma 编译时的非法指令

MSVC++ 警告标志

GCC - 仅在特定功能上启用编译器标志

GCC 警告选项 -Werror

如何编写带有目标“错误”的makefile?

是否可以使用 GCC 编译具有特定编译器标志的代码文件的一部分?