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

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


当前回答

非固定的警告迟早会导致代码中的错误。


例如,调试分段错误需要程序员追踪错误的根源(原因),它通常位于代码中比最终导致分段错误的行更前面的位置。

很典型的情况是,导致错误的行是编译器发出警告而你忽略的行,而导致分段错误的行是最终抛出错误的行。

修复警告就等于修复了问题……一个经典的!

以上的演示…考虑下面的代码:

#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伊壁鸠鲁教授曾经说过的那样:

段错误

为了在现实场景中调试这一点,人们将从导致分段错误的行开始,并试图跟踪原因的根源是什么……他们将不得不在那里的大量代码中搜索i和str发生了什么……

直到有一天,他们发现idx使用时没有初始化,因此它有一个垃圾值,这导致索引字符串(方式)超出其界限,从而导致分割错误。

如果他们没有忽视这个警告,他们就会立即发现这个漏洞!

其他回答

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


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

将警告视为错误只有一个问题:当你使用来自其他来源的代码时(例如,微软库,开源项目),他们没有正确地完成他们的工作,编译他们的代码会产生大量的警告。

在编写代码时,我总是确保它不会产生任何警告或错误,并在编译之前进行清理,而不会产生任何无关的噪音。我不得不处理的垃圾让我感到震惊,当我不得不构建一个大项目时,看着一串警告从编译器应该只声明它处理了哪些文件的地方经过时,我感到震惊。

我也记录我的代码,因为我知道软件真正的生命周期成本主要来自维护,而不是最初的编写,但这是另一回事……

编译器警告是你的朋友

我在传统的Fortran 77系统上工作。编译器告诉我有价值的东西:在子例程调用上的参数数据类型不匹配,如果我有一个变量或子例程参数没有被使用,那么在值被设置到变量之前使用一个局部变量。这些几乎都是错误。

当我的代码编译干净,97%的工作。与我一起工作的另一个人在编译时关闭了所有警告,在调试器中花费数小时或数天,然后让我帮忙。我只是用警告编译他的代码,然后告诉他要修改什么。

由于某些原因,c++中的编译器警告非常有用。

它可以告诉你,你可能在哪里犯了一个错误,这可能会影响你的操作的最终结果。例如,如果你没有初始化一个变量,或者如果你使用“=”而不是“==”(这只是例子) 它还允许向你展示你的代码不符合c++标准的地方。这很有用,因为如果代码符合实际标准,那么将很容易将代码移到其他平台。

一般来说,警告是非常有用的,可以告诉您代码中哪里有错误,这些错误可能会影响算法的结果,或者在用户使用您的程序时防止出现某些错误。