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

Disadvantages of using the `-Wextra` flag when compiling in GCC

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

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

读到这里我很惊讶。所以我开始用谷歌搜索它,但我没有得到任何答案,因为所有搜索结果都显示 "what does the -Wextra flag do?".

所以,我的问题是

In which all situations does the -Wextra flag emit uneccessary warnings?

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

Is it possible to stop the -Wextra flag from enabling the other flags that cause GCC from emitting these types of warnings?

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


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

...or warnings about missing field initializers (when you're intentionally initializing a structure with { 0 }, making the compiler zero-initialize everything else automagically).

这个论点没有实际意义,因为它已经 fixed. For example, the following code produces no missing initializer fields warning:

typedef struct { int a; int b; } S;

int main()
{
    S s = { 0 };
}

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

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

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

Camp 1 受到业界的追捧,因为他们产生的错误代码更少,缺陷更少,客户错误报告更少。如果您正在对运载火箭、卫星设备以及任何需要安全、控制非常昂贵的设备进行编程,那么需要本营的思维方式。

营地 2 显然太频繁了,您使用的许多应用程序(在您的计算机上、智能 phone、设备等)的一般软件质量都表明了这一点。

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

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

例如 -Wextra 意味着 -Wempty-body,它会警告您某些控制结构的空体,例如 ifelsewhile。但是,如果您从一方面遵循 'iterative' 实现风格,而从另一方面想要大量警告,这会让您感到不舒服。

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

void doSomething()
{
    if (validatePreConditions())
    {
         //TODO Handle it some time.
    }

    // ... Here you have real code.

}

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

但这里有一个陷阱,'in-development' 代码可接受的是在代码投入生产时修复。所以我对这种情况的选择是有不同的警告集和不同的构建配置文件。因此,例如,'developer' build 可以有空体,但任何要合并到主线中的东西 MUST NOT 都有这样的差距。

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

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

  1. 它们会产生误报。

  2. 误报比较频繁

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

结果是,许多打开 -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 制度下实现零警告计数,需要将此噪音添加到代码中。因此,您可以选择这三个中的任意两个:

  1. 清理代码

  2. 零警告计数

  3. -Wextra 警告

明智地选择你想放弃的那一个,你不会得到所有三个。

很简单;

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

当然这是不确定的,不可能事先知道某个警告是否会'save the bacon'。

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

-Wextra 消息,OTOH 很少指出真正的错误,或者可能指出一些旧代码中常见的结构,或者很难更改为表示代码的替代方式。所以有时候他们可能确实是 "too verbose".

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

对于旧代码,如果不是 "too verbose",则应将其打开。

基本上有三种类型的警告,GCC 不太擅长将它们分组或明确:

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

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

    int i = 1;
    if (INT_MAX > 0x7fffffff) <<= 31;
    
  3. 警告纯粹是启发式的,它们本身并不表示任何编程错误或程序无效,编译器只是警告您您的意图可能与您编写的内容不同。

-Wextra 打开很多 "type 3" 警告。

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

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