【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; }