【问题标题】:Rapidjson seg fault with g++ optimization使用 g++ 优化的 Rapidjson 段错误
【发布时间】:2015-09-23 12:47:38
【问题描述】:

我正在使用 Rapidjson,并注意到当我在 g++ (-O1/-O2/-O3) 中打开优化时,我遇到了分段错误。我想我已经追踪到了 rapidjson 中的 GenericValue& AddMember() 函数。

GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) {
    RAPIDJSON_ASSERT(IsObject());
    RAPIDJSON_ASSERT(name.IsString());

    Object& o = data_.o;
    if (o.size >= o.capacity) {
        if (o.capacity == 0) {
            o.capacity = kDefaultObjectCapacity;
            o.members = reinterpret_cast<Member*>(allocator.Malloc(o.capacity * sizeof(Member)));
        }
        else {
            SizeType oldCapacity = o.capacity;
            o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5
            o.members = reinterpret_cast<Member*>(allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member)));
        }
    }
    o.members[o.size].name.RawAssign(name);
    o.members[o.size].value.RawAssign(value);
    o.size++;
    return *this;
}

调试时,我可以看到 kDefaultObjectCapacity ( 正在被优化出来(这是一个静态的 const SizeType kDefaultObjectCapacity = 16)

因此行“o.capacity = kDefaultObjectCapacity;”没有被执行,并且 malloc 正在分配 0 字节然后尝试强制转换它。

为什么要删除这个静态常量?

我尝试过让 Object& o 既易变又静态,但均未奏效。 有什么想法吗?

谢谢 会

编辑: 我不能轻易地在嵌入式平台上运行测试,rapidjson 目前是使用 buildroot 构建的。我尝试了单元测试,但无法让它们达到目标。

我可以看看提供程序集,但它是大型应用程序的一部分,因此可能很难找到合适的位。

对于信息,这是调用 rapidjson 代码的方法,这似乎是问题所在:

int16_t FrontEndJSONHandlers::get_run_cfg_packer(JSONEngine& json_engine, char *message, int32_t *length)
{
Document doc;

// Need to pack an empty request to get the data
doc.SetObject();
doc.AddMember(JSONRPC_MEMBER, JSONRPC_VERSION, doc.GetAllocator());
doc.AddMember(METHOD_MEMBER, JSON_RPC_METH_GET_RUN_CFG, doc.GetAllocator());
doc.AddMember(ID_MEMBER, json_engine.GetNextMessageID(), doc.GetAllocator());

// Format the message
json_engine.FormatMessageAndRegisterResponseHandler(&doc, &message, &length, get_run_cfg_handler);

return 0;
}

如果我将 Document doc 设为静态,它不会出现段错误 - 但不确定这是否是最好的解决方法?

【问题讨论】:

  • 你能告诉我们生成的程序集,包括优化和不优化吗? (见stackoverflow.com/questions/137038/…
  • rapidjson 包中的单元测试是否通过?可能是应用程序在某处损坏了内存。

标签: c++ c segmentation-fault compiler-optimization rapidjson


【解决方案1】:

您的代码看起来非常错误,并且在某种程度上让我怀疑您也误解了您在调试器中看到的内容。先尝试修复错误,然后再担心优化器可能在做什么:

您的部分意图似乎是让 o.capacity 最终成为 &gt;(或者您可能打算使用 &gt;=)o.size

但是您是否考虑过o.size &gt; kDefaultObjectCapacity o.capacity 从零开始的情况?更严重的是,对于降低容量的代码,您关于增加 1.5 倍的评论是错误的。你可能打算oldCapacity*3/2

if (o.size >= o.capacity) {
    if (o.capacity == 0) {
        o.capacity = kDefaultObjectCapacity;
        o.members = reinterpret_cast<Member*>(allocator.Malloc(o.capacity * sizeof(Member)));
    }
    else {
        SizeType oldCapacity = o.capacity;
        o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5
        o.members = reinterpret_cast<Member*>(allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member)));
    }
}

编辑:我对上述大部分内容有误。但我仍然认为对调试器中所见内容的误解更有可能解释为优化器破坏了该代码。

提到的“90”是十进制还是十六进制?优化器当然可以跳过乘以 sizeof(Member) 并直接得到该乘法的结果。

【讨论】:

  • &gt;= 是正确的。当size == capacity 表示存储空间刚刚满,需要增加存储容量。另请注意,该行是o.capacity += (oldCapacity + 1) / 2,它添加了ceil(oldCapacity * 0.5) 以形成1.5 的增量因子。
  • 对不起,刚刚编辑了帖子,我打错了。 o.capacity 最初是 0 字节,所以它进入“if (o.capacity == 0) {”。然后跳过将 o.capacity 设置为 kDefaultObjectCapacity 的行,调用 Malloc 时 o.capacity 仍然为 0。
  • 既然您删除了我读了这么多的“90”,我的答案可能没有任何价值。我的替代答案是我仍然不相信优化器出了那么严重的错误。所以我们需要看到一个足够大的块,最好是一个可以编译的整个模块,但至少要足够包含你提到的那个常量的定义以及Object的数据成员的声明。如果这都是你下载的代码,一个链接会很好,因为我不知道如何搜索它。
  • 是的,对不起 90!好点,我应该发布一个链接。在这里:github.com/miloyip/rapidjson/blob/master/include/rapidjson/… 看起来很奇怪的函数是 AddMember,第 993 行
【解决方案2】:

我遇到了类似的问题,使用 GCC 4.9 进行编译:指定优化标志 (-On) 时出现分段错误。很容易复制,代码如下:

//...
#include "extern/rapidjson/document.h"
//...
void* MyThread(void*)
{
   std::cout << "Start of MyThread" << std::endl;

   rapidjson::Document doc;
   doc.Parse("{ \"NAME\": \"Peter\", \"AGE\": 38, \"Male\": true}");

   std::cout << "End!";

   return NULL;
}

int main(int argc, char** argv) 
{
   pthread_t thread;

   pthread_create(&thread, NULL, &MyThread, NULL);

   std::string str;
   std::cout << "Press <Return>"  << std::endl;
   std::getline(std::cin, str);

   return 0;
 }

这里的关键是在一个单独的线程中创建和解析文档。如果我们直接在main()中调用“MyThread(NULL)”,就不会出现bug。

Visual Studio 2012 的类似代码经过优化,运行良好。我运行了 VS 代码分析,没有发现错误。唯一的区别是线程:

std::thread th([] { MyThread(NULL); }); 
th.join();

================================================

在 1.0.2 版本后已修复该问题。使用最新的代码可以正常工作。

【讨论】:

  • 我建议将此作为一个新问题发布(“为什么线程版本会崩溃?”)。如果需要,您可以对此问题发表评论,链接到您的新问题。到目前为止,没有证据表明这实际上是 OP 问题的答案。
  • 虽然我建议至少在return 0; 之前调用pthread_join ...如果你从main 的末尾掉下来,那么它可能会破坏rapidjson 使用的任何静态对象,然后是你的线程尝试使用这些对象并遇到麻烦
  • 另一件事是cout 在 C++11 之前可能不是线程安全的
  • 感谢您的评论。使用 repo 中的最后一个代码解决了这个问题。在发布问题之前我确实尝试过,但我遇到了一个设置问题,导致使用旧版本(1.0.2)......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-31
  • 1970-01-01
  • 2011-06-15
  • 1970-01-01
  • 2011-12-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多