我经常听到有人说,在编译C和c++程序时,我应该“总是启用编译器警告”。为什么这是必要的?我怎么做呢?

有时我也听到我应该“把警告当作错误”。我应该吗?我怎么做呢?


当前回答

别着急:你不必这么做,也没有必要。-Wall和-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 =)


这只是编译器可以为您进行双重检查的许多事情中的三件。还有很多其他的方法,比如使用未初始化的变量。

别着急:你不必这么做,也没有必要。-Wall和-Werror是由代码重构狂人为自己设计的:它是由编译器开发人员发明的,目的是避免在用户端编译器或编程语言更新后破坏现有的构建。特性本身并不是什么,而是关于是否破坏构建的决定。

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

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

我的团队的核心产品是一个MFC程序,多年来,它产生了数百个警告。在几乎所有的案例中都被忽略了。

当出现bug时,这简直是一场噩梦。

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

我鼓励所有构建都采用“无警告”策略,并将编译器警告级别设置为相当吵闹的级别。

我们的做法是使用#pragma warning - push/disable/pop用于开发人员确定确实没问题的代码,并在调试级别使用日志语句,以防万一。

这种做法对我们很有效。

为什么要启用警告?

众所周知,C和c++编译器在默认情况下不擅长报告一些常见的程序员错误,例如:

忘记初始化变量 忘记从函数返回值 printf和scanf族中的参数与格式字符串不匹配 函数的使用没有事先声明(仅限C语言)

这些可以被检测和报告,只是通常不是默认情况;此特性必须通过编译器选项显式地请求。

如何启用警告?

这取决于你的编译器。

微软C和c++编译器理解/W1、/W2、/W3、/W4和/Wall这样的开关。至少使用/W3。/W4和/Wall可能会对系统头文件发出错误的警告,但如果您的项目使用这些选项之一进行了干净的编译,那么就使用它。这些选择是相互排斥的。

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

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

为什么我要把警告当作错误?它们只是警告!

A compiler warning signals a potentially serious problem in your code. The problems listed above are almost always fatal; others may or may not be, but you want compilation to fail even if it turns out to be a false alarm. Investigate each warning, find the root cause, and fix it. In the case of a false alarm, work around it — that is, use a different language feature or construct so that the warning is no longer triggered. If this proves to be very hard, disable that particular warning on a case by case basis.

你不希望只是把警告作为警告,即使它们都是假警报。对于发出的警告总数小于7的非常小的项目来说,这是可行的。再多一点,新的警告就很容易淹没在大量熟悉的旧警告中。不要允许这种情况发生。只是让你所有的项目编译干净。

Note this applies to program development. If you are releasing your project to the world in the source form, then it might be a good idea not to supply -Werror or equivalent in your released build script. People might try to build your project with a different version of the compiler, or with a different compiler altogether, which may have a different set of warnings enabled. You may want their build to succeed. It is still a good idea to keep the warnings enabled, so that people who see warning messages could send you bug reports or patches.

如何将警告视为错误?

这同样是通过编译器开关完成的。/WX是微软的,大多数其他人使用-Werror。在这两种情况下,如果产生任何警告,编译都会失败。

这就够了吗?

可能不是!随着优化级别的提高,编译器开始越来越仔细地检查代码,而这种仔细检查可能会发现更多的错误。因此,不要满足于警告开关本身,总是使用它们时,编译优化启用(-O2或-O3,或/O2如果使用MSVC)。

这是C的一个具体答案,以及为什么这对C来说比其他任何东西都重要。

#include <stdio.h>

int main()
{
   FILE *fp = "some string";
}

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

对于GCC,我们执行GCC -Wall -Werror。

这也是微软一些不安全API警告引起高度不满的原因。大多数编写C语言的人已经学会了将警告视为错误的艰难方法,而这些东西出现的不是同一种东西,需要不可移植的修复。