c++ - 我试过 : valgrind, _GLIBCXX_DEBUG,-fno-strict-aliasing;我该如何调试这个错误?

标签 c++ debugging gcc regression-testing compiler-flags

我有一个非常奇怪的错误,我花了几天时间试图弄清楚,所以现在我想看看是否有人有任何意见可以帮助我理解发生了什么。

一些背景。我正在从事一个软件项目,该项目涉及使用 Boost 1.45 向 Python 2.7.1 添加 C++ 扩展,因此我的所有代码都通过 Python 解释器运行。最近,我对破坏了我们的回归测试之一的代码进行了更改。这个回归测试可能对数值波动过于敏感(例如不同的机器),所以我应该解决这个问题。但是,由于此回归在产生原始回归结果的同一台机器/编译器上发生故障,因此我将结果的差异追溯到这段数字代码(可证实与我更改的代码无关):

c[3] = 0.25 * (-3 * df[i-1] - 23 * df[i] - 13 * df[i+1] - df[i+2]
               - 12 * f[i-1] - 12 * f[i] + 20 * f[i+1] + 4 * f[i+2]);
printf("%2li %23a : %23a %23a %23a %23a : %23a %23a %23a %23a\n",i,
       c[3],
       df[i-1],df[i],df[i+1],df[i+2],f[i-1],f[i],f[i+1],f[i+2]);

构造一些数值表。请注意:

  • %a 打印提供精确的 ascii 表示
  • 左边(lhs)是c[3],rhs是其他8个值。
  • 下面的输出是 i 的值远离 f、df 的边界
  • 此代码存在于 i 上的循环中,它本身嵌套了多个层(因此我无法提供一个孤立的案例来重现此代码)。

所以我克隆了我的源代码树,我编译的两个可执行文件之间的唯一区别是克隆包含一些额外的代码,这些代码甚至没有在此测试中执行。这让我怀疑这一定是内存问题,因为唯一的区别应该是代码在内存中的位置......无论如何,当我运行这两个可执行文件时,它们产生的区别如下:

diff new.out old.out 
655,656c655,656
<  6  -0x1.7c2a5a75fc046p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
<  7   -0x1.a18f0b3a3eb8p-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
---
>  6  -0x1.7c2a5a75fc006p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
>  7  -0x1.a18f0b3a3ec5cp-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
<more output truncated>

您可以看到 c[3] 中的值略有不同,而 rhs 值没有任何不同。所以一些相同的输入如何导致不同的输出。我尝试简化 rhs 表达式,但我所做的任何更改都消除了差异。如果我打印 &c[3],那么差异就消失了。如果我在我可以访问的两台不同的机器(linux、osx)上运行,则没有区别。这是我已经尝试过的:

  • valgrind(报告了 python 中的许多问题,但我的代码中没有任何问题,也没有看起来很严重的问题)
  • -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_ASSERT -D_GLIBCXX_DEBUG_PEDASSERT -D_GLIBCXX_DEBUG_VERIFY(但没有断言)
  • -fno-strict-aliasing(但我确实从 boost 代码中得到别名编译警告)

我尝试在有问题的机器上从 gcc 4.1.2 切换到 gcc 4.5.2,这个特定的、孤立的差异消失了(但回归仍然失败,所以让我们假设这是一个不同的问题)。

我能做些什么来进一步隔离问题吗?以备日后引用,有什么方法可以更快地分析或理解此类问题?例如,鉴于我对 lhs 发生变化而 rhs 没有发生变化的描述,您会得出什么结论?

编辑: 问题完全是由于 -ffast-math 造成的。

最佳答案

您可以更改程序的 float 据类型。如果使用float,可以切换成double;如果 c,f,df 是 double,您可以切换到 long double(intel 上 80 位;sparc 上 128 位)。对于 4.5.2,您甚至可以尝试使用 _float128(128 位)软件模拟类型。

对于更长的浮点类型,舍入误差会更小。

为什么添加一些代码(甚至未执行)会改变结果?如果代码大小发生变化,gcc 可能会以不同的方式编译程序。 GCC 内部有很多启发式算法,有些启发式算法是基于函数大小的。所以 gcc 可能会以不同的方式编译你的函数。

此外,尝试使用标志 -mfpmath=sse -msse2 编译您的项目,因为使用 x87(旧 gcc 的默认 fpmath)是 http://gcc.gnu.org/wiki/x87note

by default x87 arithmetic is not true 64/32 bit IEEE

PS:当您对稳定的数字结果感兴趣时,您不应该使用类似-ffast-math 的选项:http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Optimize-Options.html

-ffast-math Sets -fno-math-errno, -funsafe-math-optimizations, -fno-trapping-math, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans and fcx-limited-range.

This option causes the preprocessor macro FAST_MATH to be defined.

This option should never be turned on by any -O option since it can result in incorrect output for programs which depend on an exact implementation of IEEE or ISO rules/specifications for math functions.

这部分快速数学可能会改变结果

-funsafe-math-optimizations Allow optimizations for floating-point arithmetic that (a) assume that arguments and results are valid and (b) may violate IEEE or ANSI standards. When used at link-time, it may include libraries or startup files that change the default FPU control word or other similar optimizations.

这部分将向用户隐藏陷阱和类似 NaN 的错误(有时用户想要准确地获取所有陷阱来调试他的代码)

-fno-trapping-math Compile code assuming that floating-point operations cannot generate user-visible traps. These traps include division by zero, overflow, underflow, inexact result and invalid operation. This option implies -fno-signaling-nans. Setting this option may allow faster code if one relies on “non-stop” IEEE arithmetic, for example.

快速数学的这一部分说,编译器可以在任何地方采用默认的舍入模式(对于某些程序来说可能是错误的):

-fno-rounding-math Enable transformations and optimizations that assume default floating point rounding behavior. This is round-to-zero for all floating point to integer conversions, and round-to-nearest for all other arithmetic truncations. ... This option enables constant folding of floating point expressions at compile-time (which may be affected by rounding mode) and arithmetic transformations that are unsafe in the presence of sign-dependent rounding modes.

关于c++ - 我试过 : valgrind, _GLIBCXX_DEBUG,-fno-strict-aliasing;我该如何调试这个错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6770236/

相关文章:

c++ - `static_cast<bool>(x)` 和 `x != 0.0` 之间有区别吗?

c++ - 什么时候在C++中生成默认构造函数

c++ - 我可以知道为什么这段代码没有给出任何输出吗?

javascript - 调试 Chrome 扩展的 popup.html?

node.js - 如何从 Visual Studio 调试 C++ Electron 插件

c++ - 将一种类型的对象分配给其他类型的引用变量不起作用

c++ - Makefile 的编译给出 'ld returned, DSO missing' 错误

c++在同一类的另一个构造函数中调用构造函数

c++ - 问题编译 C++ 源

javascript - 命名匿名函数