调试实战 | 缺少 const 导致的 bug

共 3549字,需浏览 8分钟

 ·

2021-03-31 18:11

前言

最近,在项目里遇到一个很 “诡异” 的问题。明明把后面需要使用的值存起来了,可是使用的时候,却拿到了一堆垃圾数据。有可能是什么原因呢?一起来看看吧。

调查思路

这种问题排查起来比较简单。可以依次排查以下几个地方:

  1. 保存代码是否正确。
  2. 读取代码是否正确。
  3. 在保存后,读取前是否有其它代码修改了保存的值。

按照这个思路,很快定位到问题出在第一步,也就是保存的时候出了问题。

为了让各位小伙伴儿也能实际感受这个问题,我特意模仿实际项目中的代码写了一份可以重现的代码。

关键代码

AdjustInfoByteConverter 里的代码比较简单,而且已经验证过没问题,这就不贴了。感兴趣的小伙伴儿可以下载示例工程查看完整代码。

调用代码如下:

#include "stdafx.h"
#include "DemoObject.h"

int _tmain(int argc, _TCHAR* argv[])
{
    CDemoObject object;
    AdjustInfo info;
    auto bytes = AdjustInfoByteConverter::ToBytes(info);
    object.SetAdjustInfo(bytes);

    // in real project, object will be serialized, and deserialized.
    // following code may run on another thread.
    std::vector<byte> restoreBytes;
    object.GetAdjustInfo(restoreBytes);
    auto info1 = AdjustInfoByteConverter::FromBytes(restoreBytes);
    return 0;
}

说明:

在实际排查问题的时候我就是这样缩小排查范围的,调用完 SetAdjustInfo(),直接调用 GetAdjustInfo(),查看两次结果是否一致。这样可以很快缩小需要排查的范围。

在写完 AdjustInfoByteConverter::ToBytesAdjustInfoByteConverter::FromBytes 的实现后,也用了类似的办法做了验证,所以在实际项目中直接排除了这两个函数的嫌疑。

CDemoObject 类的实现如下,很规矩。

#pragma once
#include "ObjectProperty.h"
#include "AdjustInfo.h"
#include "AdjustInfoByteConverter.h"

class CDemoObject
{

private:
    ExtendPropertySet extendProperty;

public:
    void SetAdjustInfo(const std::vector<byte>& bytes)
    
{
        PropertyValue value(bytes);
        extendProperty.SetSubParam("AdjustInfo", value);
    }

    int GetAdjustInfo(std::vector<byte>& bytes) const
    
{
        PropertyValue value;
        extendProperty.GetSubParam("AdjustInfo", value);
        bytes = value.m_value.m_valueByte;
        return (int)bytes.size();
    }
};

有瑕疵的代码如下,但并不是所有情况下都有问题,你能找出这个问题吗?

#pragma once
#include <vector>
#include <map>

class PropertyValue
{

public:
    enum { VALUENULL, INT, DOUBLE, Bool, String, Block } m_type;
    PropertyValue() : m_type(VALUENULL) {}

    struct
    {

        std::vector<byte> m_valueByte;
    } m_value;

    template<class T>
    PropertyValue(T value)
    
{
        m_type = Block;
        unsigned __int32 nSize = sizeof(value);
        byte *data = (byte*)&value;
        for (unsigned __int32 i = 0; i < nSize; ++i)
        {
            m_value.m_valueByte.push_back((byte)(*(data + i)));
        }
    }

    PropertyValue(std::vector<byte>& value)
    {
        m_type = Block;
        m_value.m_valueByte = value;
    }
};

class ExtendPropertySet
{

public:
    void SetSubParam(const std::string& name, PropertyValue param)
    
{
        m_SubParamMap[name] = param;
    }

    bool GetSubParam(const std::string& name, PropertyValue& param) const
    
{
        auto it = m_SubParamMap.find(name);
        if (it != m_SubParamMap.end())
        {
            param = it->second;
            return true;
        }
        return false;
    }

private:
    std::map<std::string, PropertyValue>  m_SubParamMap;
};

根本原因

这个问题的根本原因在于:调用了错误的 PropertyValue 构造函数。

预期被调用的函数是 PropertyValue(std::vector<byte>& value),而实际调用的函数却是 template<class T> PropertyValue(T value)

因为 CDemoObject 类的 void SetAdjustInfo(const std::vector<byte>& bytes)  函数的参数是 const 的。在编译 PropertyValue value(bytes); 这行代码的时候,需要找到一个最优的构造函数,最终找到的是 template 版本的。不能把一个 const 对象丢给一个参数是非 const 的函数!

解决方案

这个问题解决起来很简单,有两种改法:

  1. 去掉 const 对象的 const属性。
  2. 改动底层代码,把非 const 版本改成 const 版本的函数。

实际项目中采用的第一种改法,因为没有权限改动底层接口,但这种改法治标不治本。

下载链接

百度云盘链接: 链接: https://pan.baidu.com/s/1a2p9YWPLtlOe6dM_j_s80w 提取码: j96h

CSDN:https://download.csdn.net/download/xiaoyanilw/14965002

总结

  • 尽量使用引用传递类对象,而且如果不想在函数内部修改这个对象的话,务必加上 const
  • 不能把一个 const 对象丢给一个非 const 参数的函数。
  • 排查问题的时候,尽可能的缩小范围。


浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报