相当奇怪的铿锵问题

时间:2014-11-25 22:27:46

标签: c++ vector g++ clang clang++

我在发布此帖子之前尝试了一些Google搜索,但说实话,我不知道该搜索什么。我有一个C ++项目,并且很高兴使用GNU编译器(g ++)。今天我尝试使用clang ++进行编译并获得了段错误。

好的,好的,我可以解决这个问题。在仔细阅读我的代码并打印一些东西之后,我就能解决问题了。然而,这个解决方案让我感到非常麻烦和困惑。

以下是这种情况:我使用的是树状数据结构,它存储了一个名为Ligament的类,但我将它存储在std :: vector中。我这样做是通过存储一个“children”的向量,它实际上只是向量中父对象和子对象之间的整数偏移量。通过这种方式,我可以使用this指针访问子节点,即

child = this[offset];

但是,这一点都不重要。这是这个问题:我有一个Ligament :: addChild(int)函数,它接受一个整数并将其推送到作为Ligament成员的向量的后面:

void Ligament::addChild(uint32_t offset){
   children.push_back(offset);
}

非常简单的东西。一般情况下,我向addChild传递一个从名为fill:

的递归函数返回的参数
//starting at root
uint32_t fill(vector<Ligament>& lVec, TiXmlElement * el){
    //store current size here, as size changes during recursion
   uint32_t curIdx = lVec.size();
   lVec.push_back(createLigament());

   //Add all of this Ligament's children
   TiXmlElement * i = el->FirstChildElement("drawable");
   for (; i; i=i->NextSiblingElement("drawable")){
      uint32_t tmp = fill(lVec, i) - curIdx;
      lVec[curIdx].addChild(tmp);

      //Does not work in clang++, but does in g++
      //lVec[curIdx].addChild(fill(lVec,i)-curIdx);
   }

   //return the ligament's index
   return curIdx;
}

在XML元素上调用fill函数,并首先深入了解其子元素。

很抱歉,如果所有这一切都不清楚,但问题的核心似乎是那个for循环中的内容。出于某种原因,我必须在将变量调用的返回值存储到addChild函数之前将其存储在变量中。

如果我不将它存储在临时变量中,似乎addChild函数不会改变子节点的大小,但我无法想象为什么。

要检查所有这些,我在这些调用之前和之后打印出子矢量的大小,并且它从未超过1.只有当我使用未直接从函数返回的值调用addChild时才会看到它工作。

我还在addChild函数内部以及for循环中调用之前打印出offset的值。在所有情况下,值都是相同的,包括clang ++和g ++。

由于问题已经解决,我能够继续前进,但这是我期望的。有什么我做错了吗?

如果我能做更多让这个问题更清楚的话,请随意对我大喊大叫。

另外:我现在意识到通过这些递归通过引用传递lVec可能是不好的,因为push_back调用可能导致地址改变。这是合法的关注吗?

编辑:

正如人们所指出的那样,我最后的关注结果与此问题有关。填充调用有可能调整向量的大小,而lVec [curIdx] =修饰符将更改向量中的元素。这些事情的发生顺序会产生严重后果。

作为后续,是否可以使用tmp变量?仍然存在重新分配的问题......我想我会使用SHR建议的地图,然后在完成所有操作时将其转换为矢量。

2 个答案:

答案 0 :(得分:5)

// Does not work in clang++, but does in g++:
lVec[curIdx].addChild(fill(lVec,i)-curIdx);

您看到的错误是由于依赖于评估顺序。由于fill(lVec, i)可能导致lVec重新分配其元素,因此如果在lVec[curIdx]之前评估fill(lVec,i),则程序将具有未定义的行为。

指定函数参数的评估顺序 - 以及确定调用哪个函数的后缀表达式。

答案 1 :(得分:2)

我认为这是未定义的行为。

你进入矢量,并在同一个命令中更改它。

一个编译器可以先执行fill,另一个编译器可以先获得lVec[curIdx]

如果是这种情况,当您使用map<uint32_t,uint32_t>而不是vector时,它将适用于两个编译器。因为地图不需要内存是顺序的。