某个同事以前用 VC 写了一个 DLL 用于提供某类通用计算,相当于一个计算模块,由我这边写 BCB 程序来调用。不过在这次的问题中发现,一旦线程调用过 pow() 这个 C 库函数来计算过一个非整数指数的幂,那么这个计算模块接下来同样的参数再次用就会得出不一样的结果。调用过 pow() 之前是一种结果,调用之后是另一种结果,现象相当稳定。
诡异就诡异在 pow() 的返回值既没有错,也没有被采用过。比如仅仅一句:
pow(2.0, 3.1);
函数返回值根本没有用来干过任何事情,相当于直接丢掉了。那么按道理来说这行代码应该不会对之前或之后的代码造成任何影响。但它确确实实影响了。
然而指数如果是整数,就不会,比如:
pow(2.0, 3.0);
那个写 DLL 的同事对此也是一头雾水。怀疑点一度被放在 VC / BCB 身上,因为它们的 CRT 不一样。但因为正好有一个测试工具,稍加改造便可以针对计算过程输出详细的结果报表,因此用来比较了一下,发现了问题:出问题的地方,有两个本来应该用来比较的 double 型输入数据正好是相等的。在 pow() 调用之前,它们的确被判断为相等。但调用 pow() 之后,计算结果显示它们被判断为不相等了。
这种事情对于常写浮点运算相关代码的程序员而言是很容易引起警惕的。浮点数不能直接用 == 之类来比较,必须去判断两数相减的绝对值是否小于某个精度。所以将这个测试结果提交给写 DLL 的同事之后,很快就定位并解决了问题。
但这里我更关注的是以下几个问题:
- pow(2.0, 3.0) 和 pow(2.0, 3.1) 的不同,使我相信 CRT 肯定对前者作了优化。这种事情,想得通,但后果可能是个坑。
- 很明显,CRT 在 pow() 被调用之后,处理浮点数时的行为模式改变了。通过观察 _statusfp() 的返回值,我发现调用前为 0,调用后变成了 0x20。但这个 0x20 是一个 Undocumented 的东西,哪怕是最新的 MSDN 上也找不到。并且我通过 _fpreset() 将状态字变回了 0,但计算模块仍然会出错,说明应该还有别的东西也被改了。这个坑是不是 VC 和 BCB 联合挖的,目前还不知道。
- 如果不知道有这个坑,就可能导致一些大麻烦。在特定的代码逻辑中,这个问题很难通过黑盒测试发现。它可能在很长一段时间内都能工作得很好,直到某一次有个用户算了一个指数带小数点的幂,然后……一切就不一样了。这简直就是逻辑炸弹嘛!让我想起了《深渊上的火》里面可怜的蓝荚和绿茎。
- MSDN 真心不是完全靠得住的。
在本文最后,还要对提供过重要帮助的 Libin Yan 表示感谢!并感谢所有关注和评论过这个 PO 的 G+ 网友!
mark 说不定以后会遇到
回复删除