为什么push_back会产生分段错误



关于项目:

我正在开发一个Opengl光线跟踪器,它能够加载obj文件并对其进行光线跟踪。我的应用程序使用assimp加载了obj文件,然后通过使用着色器存储对象将所有三角形面(顶点和索引(发送到片段着色器。基本结构将从片段着色器将结果渲染到四边形。

当我加载更大的obj-s(超过100个三角形(时,计算机需要花很多时间来进行交叉,所以我开始创建一个BVH树来加快这个过程。我的BVH根据AABB中包含的三角形面的平均中值,递归地将空间拆分为两个轴对齐的边界框。

我成功地构建了BVH树结构(在CPU上(,现在我想将其转换为一个简单的数组,然后将其发送到片段着色器(到着色器存储缓冲区(。

以下是负责将BVH根节点转换为数组的方法:

BvhNode bvhNode; //global variable
BvhNode* putNodeIntoArray() {
int size=bvhNode.getNumberOfNodes();
BvhNode nodesArray[size];
int current_index = 0;
vector<BvhNode> tempNodes;
tempNodes.push_back(bvhNode);
BvhNode current_node;
while (!tempNodes.empty()) {
current_node = tempNodes.at(0);
tempNodes.erase(tempNodes.begin());
nodesArray[current_index] = current_node;
if(!current_node.isLeaf)
{
tempNodes.push_back(current_node.children.at(0)); // Segmentation error occurs here!
tempNodes.push_back(current_node.children.at(1));
}
current_index++;
}
return nodesArray;
}

关于问题:

我不知道为什么,但当我想把第一个孩子推回到tempNodes向量时,它会给我一个分割错误(确切的位置可以从上面的评论中看到(。在我看来current_node.children.at(0)并不存在,但实际上它是根据调试器存在的。我试图编写引用(&(运算符:tempNodes.push_back(&current_node.children.at(0));,但在这种情况下,它给了我对象奇怪的坐标。我试图将函数中的变量定义为全局变量,以避免范围问题,并将current_node变量定义为指针。不幸的是,没有一个能给我更好的结果。

这是我的BvhNode类,如果它有帮助的话:

class BvhNode {
public:
BBox bBox;
int depthOfNode;
vector<BvhNode> children;
int order;
bool isLeaf;
bool createdEmpty = false;
vector<glm::vec3> primitiveCoordinates;
BvhNode() {}
BvhNode(BvhNode *pNode) {}
BvhNode(vector<glm::vec3> &primitiveCoordinates) {
this->primitiveCoordinates = primitiveCoordinates; }
void buildTree(vector<glm::vec3>& indicesPerFaces, int depth) {... }

更新1:

我根据评论更新了方法。所以我将返回值的类型改为矢量,而不是BvhNode*。该算法运行良好,直到达到将叶节点放入std::向量的过程。因此,当它开始把图的最后一层放在向量上时,它会给我这个错误:

Program received signal SIGSEGV, Segmentation fault.
0x00007fbda4d69c01 in __GI___libc_free (mem=0x555be3a9dba0) at malloc.c:3123
3123    malloc.c: No such file or directory.

我设法将七个节点(意味着树的所有深度级别,除了叶子的级别(放入向量中。我也试着运行valgring,但实际上valgrind并没有给我任何错误,不像在CLion中那样。

这是我修改过的方法。我评论了分段故障的位置和修复方法。

BvhNode bvhNode;
vector<BvhNode> putNodeIntoArray() {
int size=bvhNode.getNumberOfNodes();
// FIX:  I modified the array into an std::vector
vector<BvhNode> nodesArray(size);  
int current_index = 0;
vector<BvhNode> tempNodes;
tempNodes.push_back(bvhNode);   
BvhNode current_node;
while (!tempNodes.empty()) {
current_node = tempNodes.front();// Segmentation error!!
tempNodes.erase(tempNodes.begin());
nodesArray.at(current_index)=current_node;
nodesArray.at(current_index).children.clear();

// FIX: I also modified this not to iterate through leaves' children, because they don't exist.
if(!current_node.children.empty())  
{
tempNodes.push_back(current_node.children.at(0));
tempNodes.push_back(current_node.children.at(1));
}
current_index++;
}
return nodesArray;
}

您的向量按值将BvhNode存储在各处。这意味着,每次push_back节点时,都会调用其复制构造函数,从而在节点内复制children向量成员,后者复制自己的元素等。这基本上会导致每次插入或擦除节点时都会复制/释放完整的子树。

这反过来又会导致内存碎片,最终导致向量重新分配失败并导致segfault。

如果没有完整的代码,我可以推荐这两件事:

  1. 将子项存储为(智能(指针,而不是按值

  2. 为矢量创建一个自定义分配器,以实现更细粒度的调试,并检查分配失败

实际上问题出在Bvh树的创建过程中。因为我想以2*I+1和2*I+2的形式接触到孩子们,所以我使用了一种方法来使二叉树充满(每个级别都有最大数量的节点(。在这种方法中,我必须将空节点推送到树中。

我正在创建这样的空节点:

BvhNode emptyNode;

而不是这个:

BvhNode *emptyNode = new BvhNode();

因此,当该方法完成时,emptyNode的寿命就结束了。这导致了分割错误。

此外,还有一个问题,正如Balázs Kovacsis所指出的,我在创建平面二叉树的过程中按值存储了BvhNode。这个选项使应用程序速度慢得多,这是另一个可能导致分割错误的地方。

相关内容

  • 没有找到相关文章

最新更新