【问题标题】:Why does this divide-by-zero error only occur in optimized code?为什么这个被零除错误只发生在优化代码中?
【发布时间】:2012-10-01 16:18:55
【问题描述】:

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

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

很明显,模零运算抛出了被零除的异常,但为什么只有在打开优化编译代码时才会出现这种情况?

【问题讨论】:

  • 我建议你看一下汇编输出,看看它们是否都执行了除法。还有,什么编译器? g++?
  • 奇怪的不是优化版本抛出异常,而是非优化版本没有。相反,它做了什么,调用结果0?还是!0
  • 愿意分享完整的程序、编译器版本和编译器开关吗?我无法复制您在 g++ 4.0.1/OSX 上看到的行为。每次优化和未优化版本都会出现“浮点异常”。
  • @Oren:作为一个最小的例子,我确实尝试将代码简化为 for 循环和 if 语句,但这会改变一些事情。在那一个上,优化和非优化版本的行为是一样的,现在他们抛出一个Floating point exception。 (完整代码版,优化版为Arithmetic exception)。所以很难复制或分享......
  • 对我来说,我只在非优化代码上得到“浮点异常”错误,在优化代码上没有错误。我猜它真的未定义的。

标签: c++ optimization compiler-construction divide-by-zero


【解决方案1】:

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

【讨论】:

  • 好的,但我想了解更多。为什么优化后的版本居然抛出了异常,而未优化的版本似乎根本不在乎?
  • 试试这个是GCC,尝试用-S选项编译,得到汇编语言输出,然后比较差异。
  • 语言规范并不是 CPU 功能的最终权威。公平地说,语言要求执行操作,只有结果是 UB,公平地说微体系结构指南说如何实现操作,并且 ISA 规范说这个操作的这些操作数会引发异常。所以,还有一个故事要讲。
  • 该语言未指定必须除以零。未定义的行为意味着,例如,编译器可能会为输入发出机器代码,这将导致除以零,从而导致硬盘驱动器上的所有数据被擦除并替换为小马的动画 gif,并且仍然是完美的符合标准。未定义的行为意味着语言在此类输入的生命周期中的任何时候都没有定义行为。另请参阅 (foxtrotters.tripod.com/icelnd.gif)
  • @AndreyT:你的说法没有意义。 “抛出异常”是“未定义行为”的子集。你不能根据 UB 应该发生的常识来反驳 user60628 的具体观察。
【解决方案2】:

不断折叠。

您将 interval 声明为全局 const int 并且编译器接受了您的话。

【讨论】:

  • 什么意思?请注意,编译器无法知道interval 是否为零,因为我根据命令行开关设置它。
  • 请检查const int 和整型常量表达式之间的区别。并非每个const int 都是 ICE。
  • 他在另一条评论中说这是一个“const int”。同一源文件中的 const int 可以是整型常量表达式。
【解决方案3】:

您没有告诉我们“间隔”的设置位置。优化器可能正在执行将“间隔”设置为 0 的操作。将代码更改为

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

然后看看你是否仍然得到错误。或者更好的是,向我们展示“间隔”的价值所在。

【讨论】:

  • interval 是在程序早期设置的const int,通过读取命令行参数。所以它基本上从命令行设置为零。
  • @dwelch:如果你在const int interval = x;上抛出赋值错误,你就是一个糟糕的编译器
  • @user60628:编译器错误!没有饼干!
【解决方案4】:

你能提供一个例子来说明这个问题吗?如果优化改变了结果,那么您需要反汇编代码并比较差异。你的目标平台是什么? x86,手臂,ppc?操作系统?等等?

#包括 常量 int 间隔=BOB; int main ( 无效 ) { 诠释我,n; n=10; for(i = 0; i gcc 间隔.c -DBOB=0 -O2 -o 间隔 interval.c:在函数“main”中: interval.c:15:警告:除以零

编译器想通了...

编辑:

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

#include const int 间隔; int main (int argc, char *argv[]) { 诠释我,n; 如果(argc gcc -o 间隔interval.c interval.c:在函数“main”中: interval.c:7:错误:只读变量“interval”的赋值

请提供一个完整的例子。

很有可能使用 const 并让编译器工作意味着变量从错误的地址被拉出并得到任何发生在那里的东西,这可能是也可能不是零,这取决于那个地址是什么以及所有其他的你的代码。更改优化设置会移动该地址所在的位置、它指向的位置或在执行期间更改为的位置,直到更改结果为止。

编辑:

#include int main (int argc, char *argv[]) { const int 间隔; 诠释我,n; 如果(argc gcc -c 间隔.c interval.c:在函数“main”中: interval.c:7:错误:只读变量“interval”的赋值

编译器仍然知道它是一个只读变量,使用地址指向它的非常量变量并没有改变它的只读状态,只是摆脱了编译器错误并且在long中仍然失败跑。按照设计,例如,如果将 .text 放置在只读内存(ROM/闪存)中,那么无论您玩了多少寻址和指针游戏,您都无法更改它的运行时间,直到您删除 const 并使其成为 read/写变量。无论如何,像这样的指针操作是一个大罪,因为如果/当你优化时它可能并且最终会失败(如果你使用一个非常好的编译器而不一定是 gcc,尽管它在 gcc 上也失败了)(99.999999999999% 的时间它幸运的是它可以工作,但是当它失败并指向软件设计而不是编译器或语言时,这是可以解释的)。除非 const 是这个问题的根本原因,否则只需删除 const 并给我们一个演示问题的完整示例。可能会在一个下午或一天之内关闭。

编辑 2:

unsigned int fun ( unsigned int a ) { 常量无符号整数 b = 7; *(无符号整数 *)&b = 5; 返回(a+b); }

通过优化编译上面的代码,你会得到:

.global 乐趣 乐趣: 添加 r0, r0, #7 bx lr

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

unsigned int fun ( unsigned int a ) { 无符号整数 b = 7; *(无符号整数 *)&b = 5; 返回(a+b); } .global 乐趣 乐趣: 添加 r0, r0, #5 bx lr

这让我感到惊讶,但它展示了 const 的工作原理。

【讨论】:

  • 不不,interval 的值不是使用-D 设置的,它是通过解析和处理程序运行时参数设置的。
  • 如果你声明了一个变量 const 然后你不能在程序中改变它,你的编译器应该给你一个警告,告诉你它是错误的,例如:error: assignment of read-only variable 'interval' ,你需要发布一个完整的例子,没有这样的错误让我们解决真正的问题。
  • 您可以使用以下代码更改值:*(int *)&const_int_var = 0;不要对全局变量这样做:允许编译器将全局 const 变量放在只读内存中。
猜你喜欢
  • 1970-01-01
  • 2018-04-19
  • 2014-07-13
  • 1970-01-01
  • 2012-10-08
  • 2017-05-08
  • 1970-01-01
  • 2014-06-04
  • 1970-01-01
相关资源
最近更新 更多