【发布时间】:2013-11-21 15:55:07
【问题描述】:
编辑:请参阅问题的结尾以获取最新的答案。
我花了数周时间在我的一个软件中寻找一个非常奇怪的错误 维持。长话短说,有一个旧软件在 分发,以及需要匹配输出的新软件 老的。两者(理论上)依赖于一个公共库。 [1]然而,我不能 复制由库的原始版本生成的结果, 即使库的两个版本的源匹配。实际上 有问题的代码非常简单。原始版本看起来像这样( “voodoo”评论不是我的):[2]
// float rstr[101] declared and initialized elsewhere as a global
void my_function() {
// I have elided several declarations not used until later in the function
double tt, p1, p2, t2;
char *ptr;
ptr = NULL;
p2 = 0.0;
t2 = 0.0; /* voooooodoooooooooo */
tt = (double) rstr[20];
p1 = (double) rstr[8];
// The code goes on and does lots of other things ...
}
我包含的最后一条语句是出现不同行为的地方。在里面
原始程序,rstr[8] 的值为 101325.,并将其转换为
double[3] 并分配它,p1 具有值101324.65625。同样,tt
最终得到值373.149999999996。我已经确认了这些价值观
调试打印和检查调试器中的值(包括检查
十六进制值)。这在任何意义上都不足为奇,正如预期的那样
浮点值。
在同一版本库的测试包装器中(以及在任何调用中)
到库的重构版本),第一个分配(到tt)
产生相同的结果。 然而,p1 以 101325.0 结尾,与原始匹配
rstr[8] 中的值。这种差异虽然很小,但有时会产生巨大的
取决于p1 的值的计算变化。
我的测试包装很简单,并且与原始的包含模式相匹配 完全正确,但消除了所有其他上下文:
#include "the_header.h"
float rstr[101];
int main() {
rstr[8] = 101325.;
rstr[20] = 373.15;
my_function();
}
无奈之下,我什至不厌其烦地看了看 VC6生成的反汇编。
4550: tt = (double) rstr[20];
0042973F fld dword ptr [rstr+50h (006390a8)]
00429745 fstp qword ptr [ebp-0Ch]
4551: p1 = (double) rstr[8];
00429748 fld dword ptr [rstr+20h (00639078)]
0042974E fstp qword ptr [ebp-14h]
VC6 调用同一库函数时生成的版本 测试代码包装器(它与 VC6 为我的重构生成的版本相匹配 库的版本):
60: tt = (double) rstr[20];
00408BC8 fld dword ptr [_rstr+50h (0045bc88)]
00408BCE fstp qword ptr [ebp-0Ch]
61: p1 = (double) rstr[8];
00408BD1 fld dword ptr [_rstr+20h (0045bc58)]
00408BD7 fstp qword ptr [ebp-14h]
我能看到的唯一区别是,除了数组在内存中的存储位置和
这在程序中发生了多长时间,是领先的_
在第二个中引用rstr。通常,VC6 使用前导下划线表示
使用函数进行名称修改,但我找不到任何关于它的文档
使用数组指针进行名称修改。我也看不出为什么这些会产生
在任何情况下都会产生不同的结果,除非涉及名称修改
以不同的方式读取从指针访问的数据。
我可以确定两者之间的唯一其他区别(除了调用 context) 是原来是一个基于 MFC 的 Win32 应用程序,而 后者是一个非 MFC 控制台应用程序。这两个是另外配置的 同样的方式,它们是用相同的编译标志和反对 相同的 C 运行时。
任何建议将不胜感激。
编辑:解决方案,正如 several answers 非常有帮助地指出的那样,是检查二进制/十六进制值并比较它们以确保我认为的东西在事实上是相同的。事实证明并非如此——尽管我强烈反对。
在这里,我可以吃一些不起眼的馅饼,并承认虽然我认为我检查了这些值,但实际上我检查了一些其他密切相关的值——我只有在去的时候才发现这一点回头再看数据。事实证明,rstr[8] 中设置的值非常略有不同,因此转换为 double 突出显示了非常细微的差异,然后这些差异以我的方式在整个程序中传播注意到。
我可以根据两个程序的工作方式来解释初始化的差异。具体来说,在一种情况下,rstr[8] 是根据用户对 GUI 的输入指定的(在这种情况下也是转换计算的产物),而在另一种情况下,它是从存储它的文件中读入的一些精度损失。有趣的是,在这两种情况下,它实际上都不是完全 101325.0,即使是从存储为1.01325e5的文件中读取的情况。
这将教会我仔细检查我对这些事情的双重检查。非常感谢Eric Postpischil 和unwind 提示我再次检查并及时反馈。这非常很有帮助。
脚注
- 实际上,原来的“库”是一个包含所有
内联完成的实现。标头是通过
#include和 通过extern语句引用的函数。我已经解决了这个问题 库的重构版本实际上是一个库,但请参阅 剩下的问题。 - 请注意,变量名不是我的,而且很糟糕。同样与
使用全局变量,这在这个软件中很猖獗。我离开了
在
/* voooooodoooooooooo */评论中,因为它说明了…… 不寻常的……我的前任的编程实践。我认为那个元素是 存在,因为这最初是从 Fortran 和开发人员翻译而来的 曾将其用作处理某种内存错误的方法。线路有 对代码的实际行为没有任何影响。 - 我很清楚这里实际上不需要演员,但是这个 是原始库的工作方式,我无法修改它。
【问题讨论】:
-
请注意,文字
373.15是双精度数。您将其分配给浮点数。浮点数无法准确表示373.15。但是,如果来源相同,那有什么不同呢?不同的编译器?编译器设置?对 fesetround() 的不同调用,使用非标准 _control_fp() ? -
正确,这就是为什么我对程序的原始行为并不感到惊讶,其中转换为 double 后显示的值是长小数而不是精确的。
-
注意:一般来说,典型的
double需要 17 个十进制数字来唯一地表示其值。101324.65625显示 11 和 373.149999999996 显示 15。精确度可能不是这里的问题,但可能有帮助。此外 - 我怀疑您确定rstr[8]具有值 101325 是由于具有整数截断的rstr[8]的整数视图,它实际上具有 101325 21/32 的 精确 值。跨度> -
显示(从两个代码中)rstr[20] 的十六进制值和此分配后的 tt 值 tt = (double) rstr[20] - 与 rstr[8] 和 p1 var 相同。恕我直言 - 你的调试方法有问题,或者你误解了你看到的东西。
-
@chux,如果有整数截断,它在调试器视图中。这当然是可能的,但它报告的不是
101325,而是101325.——注意小数点。它可能不准确地将其报告为整数小数,如果是这样,那不是我在 VC6 中发现的第一个错误,但它可能不是截断。
标签: c assembly x86 visual-c++-6 disassembly