【segmentation fault】std::string析构崩溃

今天写了一个小工具,运行时发生segmentation fault,现象如下

第一步:review崩溃附近代码,产生疑惑,崩溃的地方居然是变量定义的地方

std::string accessToken;

崩溃在这个地方,我直接懵了,只是变量定义为啥会报错,没有任何思路,打算单步调试。

第二步:单步调试代码,发现并且是定义的时候崩溃,而是当前函数执行完成,释放栈变量的时候崩溃,此时再看崩溃栈,发现也是崩溃在~basic_string()析构函数中

第三步:怀疑是自己对accessToken赋值产生问题,难道是不能将char*赋值给std::string?去网上搜索一下,发现很多文章说char*赋值std::string会崩溃,

我认为是自己对std::string隐式转换使用错误,再想想不对,我对std::string隐式转换用法是正确的。

我开始修改accessToken的赋值,我发现accessToken赋值的字符很少的时候,程序就不会崩溃了,赋值很长的时候就会崩溃。(当时没有想明白,后来就清楚了)

第四步:陷入困境,我只能怪自己不精通c++,于是用c来改写,把accessToken的类型换成char数组,问题得到解决。

第五步:解决完问题后,我继续分析accessToken字符内容少的时候没有问题,而accessToken内容长的时候却会崩溃。我自己写了一个简单的程序,验证了std::string赋值很长的字符串也是没有问题的。此时我想明白了,肯定是我哪个地方内存越界了,引发了这种奇怪的现象,现象越奇怪,说明可能是内存越界引发的BUG

第六步:采取注释法,发现把url操作这段逻辑注释掉就运行正常了,发现url数组操作越界了。如下图

小结:
对于字符串拼接操作,不允许使用sprintf,必须使用snprintf来代替。

int NacosHelper::queryConfiguration(const std::string& dataId, std::string& result)
{
    int ret = 0;
    const char* func = "queryConfiguration";

    std::list <std::string> headers;
    std::string encoding;
    long readTimeoutMs = 300000;
    std::string paramValues;
    char url[256] = { 0 };
    std::string resp;
    std::string accessToken;

    // 1.参数校验
    if (dataId.empty())
    {
        ret = -1;
        fprintf(stderr, "%s invalid parameter\n", func);
        return ret;
    }

    // 2.登录
    ret = this->login(accessToken);
    if (ret)
    {
        ret = -1;
        return ret;
    }

    // 3.请求注册中心

    // 3.1 拼接url
    if (accessToken.empty())
    {
        sprintf(url, "%s/nacos/v1/cs/configs?dataId=%s.%s&group=%s&tenant=%s", 
            this->_host.c_str(), this->_projectName.c_str(), dataId.c_str(), 
            this->_group.c_str(), this->_namespace.c_str());
    }
    else
    {
        sprintf(url, "%s/nacos/v1/cs/configs?dataId=%s.%s&group=%s&tenant=%s&accessToken=%s",
            this->_host.c_str(), this->_projectName.c_str(), dataId.c_str(),
            this->_group.c_str(), this->_namespace.c_str(), accessToken.c_str());
    }

    // 3.2 请求配置中心
    paramValues = "";
    ret = this->_helper->Get(url, headers, paramValues, encoding, readTimeoutMs, result);
    if (ret)
    {
        ret = -1;
        fprintf(stderr, "%s exec curl error .\n", func);
        return ret;
    }

    if (result.empty())
    {
        ret = -1;
        fprintf(stderr, "%s query configuration %s response empty .\n", func, url);
        return ret;
    }

    return ret;
}


int NacosHelper::parseToken(const std::string& body, std::string& accessToken)
{
    int ret = 0;
    const char* func = "parseToken";

    cJSON* pstRoot = NULL;
    cJSON* pstNode = NULL;

    // 1.参数校验
    if (body.empty())
    {
        ret = -1;
        fprintf(stderr, "%s invalid parameter\n", func);
        return ret;
    }

    do 
    {
        // 2. 解析json
        pstRoot = cJSON_Parse(body.c_str());
        if (NULL == pstRoot)
        {
            fprintf(stderr, "%s parse response json error .\n", func);
            break;
        }

        // 3.提取accessToken
        pstNode = cJSON_GetObjectItem(pstRoot, "accessToken");
        if (NULL == pstNode || cJSON_String != pstNode->type)
        {
            fprintf(stderr, "%s invalid data type\n", func);
            break;
        }

        accessToken = pstNode->valuestring;

    } while (0);

    // 释放资源
    if (pstRoot)
    {
        cJSON_Delete(pstRoot);
        pstRoot = NULL;
    }

    return ret;

}

 

posted on 2023-04-06 20:27  寒魔影  阅读(1170)  评论(0编辑  收藏  举报

导航