4

我看到一些发布的代码在 SO 上出现超出范围的错误,这让我感到奇怪。我希望编译器为此代码生成警告(至少在最高级别)

#pragma warning(push,4)
int main(){
    int x[2];
    x[2]=0;     
    return 0;
}
#pragma warning(pop)

但事实并非如此。

EDG 编译器很好地说:

"sourceFile.cpp", line 3: warning:
          subscript out of range
          x[2]=0;
          ^

实际上 EDG 说的更多(所有这些都是意料之中的)

"sourceFile.cpp", line 1: warning: 
          unrecognized #pragma
  #pragma warning(push,4)
          ^

"sourceFile.cpp", line 4: warning: 
          subscript out of range
      x[2]=0;     
      ^

"sourceFile.cpp", line 3: warning: 
          variable "x" was set but never used
      int x[2];
          ^

"sourceFile.cpp", line 7: warning: 
          unrecognized #pragma
  #pragma warning(pop)

但这不是我的问题。

我认为这种失败是在警告 VC9 中的严重遗漏错误,(因为自动变量更是如此!!!!)。谁能给我一个改变主意的严肃理由?

4

7 回答 7

17

许多编译器可以选择错误排除这种事情。

但是对于 C 编译器来说,默认情况下放开它是非常传统的,甚至是合适的。这有多种原因。

  1. 请记住,x[i]andi[x]在 C 中是一样的。你甚至可以做"string"[2]OR 你可以做2["string"]并得到相同的结果。试试看。这是因为x[i]定义为*(x + i)并且一旦 C 只是进行指针运算,然后取消引用表达式的结果,它就不会在编译器的域中来决定它是否可以工作。

  2. 鉴于指针算法是合法的,许多相当不错的设计模式实际上依赖于技术下标违规

    结构 s {
        ……一堆东西……
        整数点[1];// 不是真的 [1]
    };
    ...
    struct s *p = malloc(sizeof (struct s) + someNumber * sizeof(int));
    

    今天到处都有这样的代码在运行......    更新:嘿,这是一个关于 stackoverflow 的 struct hack的实际示例。

于 2009-09-02T00:35:57.753 回答
8

编译器不需要为未定义的行为发出警告(即使是像这样的“严重”行为)。许多编译器具有他们倾向于检查的不同行为集。我认为,如果您有 VSTS,则可以启用一些额外的安全检查,因此这可能会被捕获。此外,编译器可以插入将捕获此内存覆盖的运行时检查(可能用于调试构建),因此您应该确保启用了这些。

于 2009-09-02T00:19:45.357 回答
7

虽然给出的示例非常简单,但在编译期间做好静态分析通常会占用大量代码并减慢编译速度(天真的实现意味着再次通过 AST)。

在一种已经经常因编译缓慢而受到指责的语言中。

让你变得愚蠢是 C++ 的一部分。试图将您从自己手中拯救出来的编译器很好,但那是肉汁。

FWIW:g++ -Wall也没有警告。

于 2009-09-02T01:07:25.747 回答
5

对源执行静态代码分析时会发出此警告。不过,静态代码分析不是编译器规范的一部分(至少据我所知),而是由单独的工具完成的。

下面是C/C++ 代码分析的概述。以及该工具涵盖的警告列表。

于 2009-09-02T00:34:51.637 回答
3

它没有对此发出警告的原因是它很少有用。查看您的代码:

int main(){
    int x[2];
    x[2]=0;     
    return 0;
}

我们看到这种情况下的编译器能够发出警告,但这仅仅是因为:

  • 数组还没有衰减为指针——换句话说,大小信息仍然可用,并且
  • 您正在使用编译时常量表达式来索引数组。

在大多数实际代码中,这两个条件都不成立。数组几乎总是一个指针,在这种情况下编译器根本没有大小信息。同样,您经常使用运行时确定的值来索引数组。同样,如果您这样做,编译器将无法确定您是否可能超出范围。

换句话说,虽然是的,但在这种情况下,编译器可能会发出警告,我同意,它也可以,但它实际上只会在像这样的非常简单的玩具示例中有所帮助。

如果代码看起来像这样:

void foo(int* x){
    x[2]=0;     
}

或这个:

void foo(int i){
    int x[2];
    x[i]=0;
}

编译器会很无奈。而这些情况要普遍得多。正如其他人已经提到的,C++ 编译器的最大问题之一是它们已经非常慢了。他们必须检查的每个新警告都会增加更多开销。那么是否值得为基本上只发生在像这样的小玩具示例中的错误添加警告?

至于为什么你得到如此糟糕的回应,也许答案就在你的问题中:

我认为这种失败是在警告 VC9 中的一个严重遗漏错误,(因为自动变量**!!!!** 更是如此)。谁能给我一个改变主意的严肃理由?

减少加载的语言。坚持一个感叹号。如果你听起来像是要引爆保险丝,那么人们会认为你很愤怒,然后他们会告诉你你反应过度,你应该闭嘴。

于 2009-09-08T21:52:03.980 回答
0

因为,编译器不是你的保姆。

于 2009-09-02T00:28:28.310 回答
0

有几点可能值得注意:

  1. 除非您关闭所有优化,否则这将编译为空。内存根本不会被写入——你也可以这样写:

    #pragma warning(push,4)
    int main(){
        return 0;
    }
    #pragma warning(pop)
    
  2. 如其他地方所述,进行这样的分析很难(如不可计算的尝试解决停止问题)。在做优化的时候,只在分析容易的情况下做就可以了。在查找警告时,需要在能够找到简单案例和让人们依赖警告之间进行权衡。什么时候可以停止发出警告?是的,由常量偏移量访问的本地声明的变量是微不足道的 - 但由于微不足道,它也不太重要。如果访问是在一个简单的内联函数中怎么办?或者,如果访问是在具有恒定边界的 for 循环中?这些都很容易寻找,但它们都代表了一组全新的测试、可能的回归等……几乎没有什么好处(如果有的话)。

我并不是说这个警告没有用——它只是不像你想象的那么明确。

于 2009-09-08T18:50:49.800 回答