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

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

前言

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

调查思路

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

  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 参数的函数。
  • 排查问题的时候,尽可能的缩小范围。
相关文章
|
4月前
|
测试技术 API
修改bug引入更多bug怎么办?
修改bug引入更多bug怎么办?
|
3月前
|
Go 开发者 UED
Go错误处理方式真的不好吗?
Go错误处理方式真的不好吗?
19 0
|
8月前
|
安全 编译器 Go
读<一例 Go 编译器代码优化 bug 定位和修复解析>
读<一例 Go 编译器代码优化 bug 定位和修复解析>
83 0
|
11月前
|
IDE 编译器 程序员
该学会是自己找bug了(vs调试技巧)
该学会是自己找bug了(vs调试技巧)
63 0
|
前端开发 JavaScript Sentinel
我发现了axios源码工具函数中的一个小bug
最开始我一看很蒙蔽,很多时候自己并不会去写这样的函数,说白了还是自己代码底子不行。可能很多大佬一看就明白了,所以基础很重要,基础很重要,基础很重要。箭头函数算是ES6中新增的。
72 0
|
XML 安全 数据格式
测试妹子提了个bug,为什么你多了个options请求?
对于简单请求来说,如果请求跨域,那么浏览器会放行让请求发出。浏览器会发出cors请求,并携带origin。此时不管服务端返回的是什么,浏览器都会把返回拦截,并检查返回的response的header中有没有Access-Control-Allow-Origin是否为true,说明资源是共享的,可以拿到。如果没有这个头信息,说明服务端没有开启资源共享,浏览器会认为这次请求失败终止这次请求,并且报错。
173 0
测试妹子提了个bug,为什么你多了个options请求?
|
NoSQL Shell C语言
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(二)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
351 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(二)
|
存储 NoSQL IDE
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解
273 0
【五、深入浅出GDB调试器】如何修复程序bug或优化代码:gdb调试器的来龙去脉与debug全方位实战详解(三)
|
NoSQL 程序员 编译器
常用的调试技巧(如何检测bug)(一)
常用的调试技巧(如何检测bug)
常用的调试技巧(如何检测bug)(一)
|
程序员
常用的调试技巧(如何检测bug)(二)
常用的调试技巧(如何检测bug)