我试过:瓦尔格林德,_GLIBCXX_DEBUG,-fno-strict-aliasing;如何调试此错误

I tried: valgrind, _GLIBCXX_DEBUG, -fno-strict-aliasing; how do I debug this error?

本文关键字:何调试 错误 调试 -fno-strict-aliasing GLIBCXX 林德 DEBUG      更新时间:2023-10-16

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

一些背景。我正在做一个软件项目,该项目涉及使用 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 %23an",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 个值。
  • 下面的输出是针对远离 f、df 边界的 i 值
  • 这段代码存在于 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(但我确实从提升代码中得到了别名编译警告(

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

我能做些什么来进一步隔离问题?为了将来参考,有没有办法更快地分析或理解这类问题?例如,鉴于我对 lhs 变化的描述,即使 rhs 没有变化,你会得出什么结论?

编辑:问题完全是由于-ffast-math.

您可以更改程序的浮点数据类型。如果使用浮点数,则可以切换到双精度;如果 cfdf 是双精度,则可以切换到长双精度(英特尔为 80 位;SPARC 为 128 位(。对于 4.5.2,您甚至可以尝试使用 _float128(128 位(软件模拟类型。

使用较长的浮点类型,舍入误差会更小。

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

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

默认情况下 x87 算术不为真 64/32 位 IEEE

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

-ffast-math 设置 -fno-math-errno, -funsafe-math-optimizations, -fno-trapping-math, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans 和 fcx-limited-range。

此选项会导致定义预处理器宏FAST_MATH

此选项不应由任何 -O 选项打开,因为它可能导致依赖于数学函数的 IEEE 或 ISO 规则/规范的精确实现的程序输出不正确

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

-funsafe-math-optimizations 允许对浮点运算进行优化,这些算法 (a( 假定参数和结果有效,以及 (b( 可能违反 IEEE 或 ANSI 标准。在链接时使用时,它可能包括更改默认 FPU 控制字或其他类似优化的库或启动文件。

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

-fno-trapping-math 编译代码时假定浮点运算不能生成用户可见的陷阱。这些陷阱包括除以零、溢出、下溢、结果不准确和无效操作。此选项意味着 -fno-signaling-nans。例如,如果依赖于"不间断"的IEEE算术,则设置此选项可能会允许更快的代码。

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

-fno-rounding-math 启用采用默认浮点舍入行为的转换和优化。对于所有浮点到整数的转换,这是舍入到零,对于所有其他算术截断,这是舍入到最接近的。...此选项允许在编译时对浮点表达式进行常量折叠(可能会受到舍入模式的影响(,以及在存在与符号相关的舍入模式时不安全的算术转换。