【crash】浮点除0堆损坏

  • 摘要:工作中遇到一个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中的元素一部分的pinf,比较反常,因此排查了该值的计算来源发现是有double除0。然后将该处加了check后就不会崩溃了。
  到现在为止崩溃的原因基本已经确定了,除0UB导致的问题。

3 具体原因

  我个人一直以为除0一定会出现除0异常,所以最初并没有怀疑此处,而简单测试发现:

  • int整型除0一定会崩溃;
  • double除0会得到inf。

【crash】浮点除0堆损坏_第1张图片
  IEEE754规定了浮点除0运算的结果为+INF或者-INF,但是C++标准明确规定了该行为未定义的,使用可能会导致UB。实际测试过程中发现只有低版本的机器浮点除0是不支持的,而新机器无论是windows还是Mac浮点除0编译器只会报warning但是结果是INF不会出错。

【crash】浮点除0堆损坏_第2张图片

  具体原因是,C++并不强制编译器实现IEEE 754标准,因为可能受限于硬件平台无法实现。所以上面问题的结论是:

  1. 整数除0,必现除0异常;
  2. 浮点除0,不同编译器不同硬件平台现象不同不具备可移植性,应该按照C++的标准描述,是UB行为:
    1. C标准附件F(IEC 60559/IEEE754)定义了浮点除0的结果;
    2. 如果硬件的FLU且编译器支持IEC 559,那浮点除0返回+inf/-inf都是符合预期的:
      1. 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

你可能感兴趣的:(c++,bug,c++)