- 摘要:工作中遇到一个crash,其现象真实的crash原因差别比较大,和我自身原本了解的只是冲突,因此在本片文档中简单描述下。
- 关键字:除0、IEEE754
1 前情提要
QA测试过程发现一个比较奇怪的crash,只会在特定机型特定素材上出现,而且出现crash的位置也比较诡异,具体堆栈如下:
0xxxxxxx split_buffer::construct_at_end
0xxxxxxx XXX::doProcess
crash的具体原因是除0。
2 排查
首先就是根据上面的堆栈分析可能crash的原因,因为是在vector
在已经分配的内存上创建对象时崩溃,我们基本可以断定,这个vector
的内存出现了问题。然后就去看代码排查调用链中可能导致堆损坏的地方(因为开发机无法复现,因此只能通过分析+日志来排查),在排查代码过程中发现了几处可能越界的操作以及除0。因为是堆损坏就朝着越界的方向去排查了,但是加了新的日志之后发现可能存在越界的代码实际上没有越界,而且实际崩溃的地方大概如下:
std::sort(vec.begin(), vec.end(), [](const bbox& a, const bbox& b){ return a.p > b.p; //崩溃的地方
});
具体崩溃是在访问a
时出现的,此时就有点儿匪夷所思了,崩溃堆栈是construct_at_end
这个只有需要在vector
中新建元素才会调用的函数,但是std::sort
只涉及元素的交换(因为对应的vector
比较小只有20个元素)。
然后在排查是通过日志发现vector
中的元素一部分的p
是inf
,比较反常,因此排查了该值的计算来源发现是有double除0。然后将该处加了check后就不会崩溃了。
到现在为止崩溃的原因基本已经确定了,除0UB导致的问题。
3 具体原因
我个人一直以为除0一定会出现除0异常,所以最初并没有怀疑此处,而简单测试发现:
- int整型除0一定会崩溃;
- double除0会得到inf。
IEEE754规定了浮点除0运算的结果为+INF或者-INF,但是C++标准明确规定了该行为未定义的,使用可能会导致UB。实际测试过程中发现只有低版本的机器浮点除0是不支持的,而新机器无论是windows还是Mac浮点除0编译器只会报warning但是结果是INF不会出错。
具体原因是,C++并不强制编译器实现IEEE 754标准,因为可能受限于硬件平台无法实现。所以上面问题的结论是:
- 整数除0,必现除0异常;
- 浮点除0,不同编译器不同硬件平台现象不同不具备可移植性,应该按照C++的标准描述,是UB行为:
- C标准附件F(IEC 60559/IEEE754)定义了浮点除0的结果;
- 如果硬件的FLU且编译器支持IEC 559,那浮点除0返回+inf/-inf都是符合预期的:
- clang和msvc针对该行为都会报warning,能够正常运行得到inf。
参考资料
- Why does division by zero in IEEE754 standard results in Infinite value?
- The behaviour of floating point division by zero
- expr