文章

Bug:从map中取值时崩溃

Bug:从map中取值时崩溃

总结

paraMap 中不包含元素时,如下代码会返回 true:

1
2
3
4
5
6
7
bool testFunc(std::map paraMap) 
{
    if(0 > paraMap.size() - 1) {
        return false;
    }
    return true;
}

现象:

  • AOS2 服务崩溃,log 中没啥参考价值,直接看 dmp 日志:

    0x008E067E (TKAOS2Service.exe) (10.30.126.106_TKAOS2Service250806-201609.dmp 中)处有未经处理的异常: 0xC0000005: 读取位置 0x0054005C 时发生访问冲突。

  • 根据 dmp 日志定位到如下函数,是从 it 中取值时崩溃:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    // 获取map中第num个对象的信息,返回一个string
    std::string CAOS2DevID::GetClsInfo(int num) const
    {
      if (num < 0 || num > 3) {
          return "";
      }
      if(num > m_mapDevField.size() -1) {   // 当 map 为空时,此处不会直接 return
          return "";
      }
      auto it = m_mapDevField.begin();
      std::advance(it, num);
    
      std::string strField = GetStrFromField(it->first); // dmp日志显示在这里崩溃
      std::string strID = it->second.strID;
      std::string strErrCode = std::to_string(it->second.ecCode);
      std::string strECMsg = aos2::ErrorCode2Str(it->second.ecCode);
    
      std::ostringstream oss;
      oss << "设备标识[" << strField << "]:" << strID 
          << ", 错误码为: " << strErrCode << ", 错误信息为: [" << strECMsg << "]";
      return oss.str();
    }
    

问题与原因:

  • 经复现,发现当 m_mapDevField 为空时,如下代码不会直接 return!继续往下执行就会出现崩溃。
    1
    2
    3
    
      if(num > m_mapDevField.size() -1) {
          return "";
      }
    

分析:

  • std::map 的 size() 返回值是无符号类型(通常是 size_t)。当 m_mapDevField 为空时:
  • m_mapDevField.size() 的值为 0
  • m_mapDevField.size() - 1 会发生无符号数下溢,结果变为该类型的最大值(例如 size_t 为 32 位时是 4294967295)
  • 此时原代码的判断条件 if(num > m_mapDevField.size()-1) 就会变成:
    1
    2
    
    // 当size() == 0时,等价于:
    if(num > 4294967295)  // num的取值是0~3,显然不成立,因此不会直接return
    
  • 条件判断失效,代码会继续向后执行 std::advance(it, num),但此时容器为空,迭代器 it 初始就是 end(),再向前移动 num 步会导致迭代器越界到非法内存区域,最终访问 it 时触发内存访问冲突。

解决方案:

1
2
3
4
5
6
7
8
9
// 增加对迭代器的保护:
if (it == m_mapDevField.end()) {   // 确认迭代器的有效性
    return "";
}
// 或:
// 修改判断逻辑为:
if(num + 1 > m_mapDevField.size()) {
    return "";
}

思考:

  • 在处理容器迭代器时,直接检查迭代器是否有效比依赖 size() 计算更安全
  • 在会涉及到隐式转换的地方,应该尤其注意数据类型
本文由作者按照 CC BY 4.0 进行授权