代码之家  ›  专栏  ›  技术社区  ›  Frank

为什么这种被零除的错误只发生在优化的代码中?

  •  3
  • Frank  · 技术社区  · 14 年前

    我刚发现一个错误,奇怪的是,只有在优化被打开时才会发生。( g++ -O2 )它是一个 Arithmetic exception 在以下代码中,当 interval 已设置为零(来自命令行参数):

    for(int i = 0; i < n; ++i) {
      if((i + 1) % interval == 0) { // exception here
        DoSomething();
      }
    }
    

    很明显,模零操作引发了一个被零除的异常,但是为什么只有在优化打开的情况下编译代码时才会发生这种情况呢?

    4 回复  |  直到 12 年前
        1
  •  13
  •   SingleNegationElimination    14 年前

    被零除总是未定义的行为。使用不同的优化设置获得不同结果的事实仍然符合未定义行为的定义。

        2
  •  1
  •   Joshua    14 年前

    不断折叠。

    您将interval声明为全局const int,编译器会立即接受您的命令。

        3
  •  0
  •   selfsimilar    14 年前

    你不告诉我们“间隔”是在哪里设置的。优化器可能正在执行将“interval”设置为0的操作。将代码更改为

    for(int i = 0; i < n; ++i) {
      if (0==interval) { break; }
      if((i + 1) % interval == 0) { // exception here
        DoSomething();
      }
    }
    

    看看你是否仍然得到错误。或者更好的是,告诉我们“间隔”的值在哪里。

        4
  •  0
  •   old_timer    14 年前

    你能举个例子来说明这个问题吗?如果优化更改了结果,那么需要分解代码并比较差异。你的目标平台是什么?x86,ARM,PPC?操作系统?等?

    #include 
    const int interval=BOB;
    int main ( void )
    {
        int i,n;
        n=10;
        for(i = 0; i < n; ++i)
        {
            if((i + 1) % interval == 0)
            { // exception here
                printf("%d\n",i);
            }
        }
        return(0);
    }
    
    gcc interval.c -DBOB=0 -O2 -o interval
    interval.c: In function ‘main’:
    interval.c:15: warning: division by zero
    

    编译器发现了…

    编辑:

    如果您试图从命令行参数分配它,您应该得到一个编译器错误,结果是没有任何东西可以执行。

    #include <stdio.h>
    const int interval;
    int main ( int argc, char *argv[] )
    {
        int i,n;
        if(argc<2) return(1);
        interval=atoi(argv[1]);
    
        n=10;
        for(i = 0; i < n; ++i)
        {
            if((i + 1) % interval == 0)
            { // exception here
                printf("%d\n",i);
            }
        }
        return(0);
    }
    
    gcc -o interval interval.c
    interval.c: In function ‘main’:
    interval.c:7: error: assignment of read-only variable ‘interval’
    

    请提供一个完整的示例。

    很有可能,使用const并让编译器工作意味着变量被从错误的地址中提取,并得到任何可能为零或不可能为零的地方,这取决于该地址是什么以及您的所有代码。更改优化设置将移动到该地址所在的位置、它指向的位置或在执行过程中更改的位置,直到更改结果为止。

    编辑:

    #include <stdio.h>
    int main ( int argc, char *argv[] )
    {
    const int interval;
        int i,n;
        if(argc<2) return(1);
        interval=atoi(argv[1]);
    
        n=10;
        for(i = 0; i < n; ++i)
        {
            if((i + 1) % interval == 0)
            { // exception here
                printf("%d\n",i);
            }
        }
        return(0);
    }
    
    
    gcc -c interval.c 
    interval.c: In function ‘main’:
    interval.c:7: error: assignment of read-only variable ‘interval’
    

    编译器仍然知道它是一个只读变量,使用地址将一个非常量变量指向它,不会改变它的只读状态,只需消除编译器错误,从长远来看仍然会失败。例如,如果文本被放置在只读存储器(ROM/flash)中,那么无论你玩多少寻址和指针游戏,你都不能改变它的运行时间,直到你删除常量并使其成为一个读/写变量。无论如何,像这样的指针操作是一个主要的罪过,因为它可以并且最终会在优化时失败(如果使用真正好的编译器,而不一定是GCC,尽管它在GCC上也失败了)(99.9999999999%的时间是幸运的,它能工作,但在它工作时是可以解释的失败并指向软件设计,而不是编译器或语言)。除非const是这个问题的根本原因,否则只需删除const并给我们一个完整的例子来演示这个问题。在一个下午或一天之内,这可能会关闭。

    编辑2:

    unsigned int fun  ( unsigned int a )
    {
        const unsigned int b = 7;
        *(unsigned int *)&b = 5;
        return(a+b);
    }
    

    通过优化编译上述内容,您可以得到:

        .global fun
    fun:
        add r0, r0, #7
        bx  lr
    

    如预期的那样,const使b为只读。没有常量:

    unsigned int fun  ( unsigned int a )
    {
        unsigned int b = 7;
        *(unsigned int *)&b = 5;
        return(a+b);
    }
    
        .global fun
    fun:
        add r0, r0, #5
        bx  lr
    

    我对此感到惊讶,但却从未少过这一点。