【问题标题】:Getting Segmentation fault while loading OBJ file加载 OBJ 文件时出现分段错误
【发布时间】:2019-11-18 22:12:05
【问题描述】:

将这段代码添加到我的 OpenGL 程序后,我在 C++ 中使用 gcc 得到“分段错误(核心转储)”

vector<GLfloat> vecTofloat(vector<glm::vec3> veca){
    vector<GLfloat> fa;
    for (int i = 0; i < veca.size(); i++){
        glm::vec3 vec = veca[i];
        fa.push_back(vec.x);
        fa.push_back(vec.y);
        fa.push_back(vec.z);
    }
    return fa;
}

vector<GLfloat> vecTofloat(vector<glm::vec2> veca){
    vector<GLfloat> fa;
    for (int i = 0; i < veca.size(); i++){
        glm::vec2 vec = veca[i];
        fa.push_back(vec.x);
        fa.push_back(vec.y);
    }
    return fa;
}

MeshInstance loadOBJ(const char* path, const char* texPath, int tw, int th){
    vector<glm::vec3> vert;
    vector<glm::vec3> tvert;
    vector<GLuint> ivert;
    vector<glm::vec3> norm;
    vector<glm::vec3> tnorm;
    vector<GLuint> inorm;
    vector<glm::vec2> tex;
    vector<glm::vec2> ttex;
    vector<GLuint> itex;

    FILE* file = fopen(path, "r");
    if (file == NULL){
        LOG_ERROR("Unable to load OBJ mesh.");
        exit(-1);
    }

    while(true){
        char start[128];

        int res = fscanf(file, "%s", start);

        if (res == EOF){
            break;
        } else if (strncmp(start, "v", strlen("v")) == 0){
            glm::vec3 vertex;
            fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
            tvert.push_back(vertex);
        } else if (strncmp(start, "vt", strlen("vt")) == 0){
            glm::vec2 tex;
            fscanf(file, "%f %f\n", &tex.x, &tex.y);
            ttex.push_back(tex);
        } else if (strncmp(start, "vn", strlen("vn")) == 0){
            glm::vec3 nrm;
            fscanf(file, "%f %f %f\n", &nrm.x, &nrm.y, &nrm.z);
            tnorm.push_back(nrm);
        } else if (strncmp(start, "f", strlen("f")) == 0){
            std::string vertex1, vertex2, vertex3;
            unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
            fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n",&vertexIndex[0],&uvIndex[0],&normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );

            ivert.push_back(vertexIndex[0]);
            ivert.push_back(vertexIndex[1]);
            ivert.push_back(vertexIndex[2]);
            itex.push_back(uvIndex[0]);
            itex.push_back(uvIndex[1]);
            itex.push_back(uvIndex[2]);
            inorm.push_back(normalIndex[0]);
            inorm.push_back(normalIndex[1]);
            inorm.push_back(normalIndex[2]);
        }
    }

    for (int i = 0; i < ivert.size(); i+=3){
        unsigned int vertex = ivert[i];
        vert.push_back(tvert[vertex - 1]);
    }

    for (int i = 0; i < itex.size(); i+=3){
        unsigned int vertex = itex[i];
        tex.push_back(ttex[vertex - 1]);
    }

    for (int i = 0; i < inorm.size(); i+=3){
        unsigned int vertex = inorm[i];
        norm.push_back(tnorm[vertex - 1]);     
    }

    return LoadToVAO(vecTofloat(vert), ivert, vecTofloat(tex), texPath, tw, th);
}

它所做的只是加载一个 obj 文件。我知道这是由于访问的内存已被删除,但我无法弄清楚程序中发生分段错误的位置。

编辑: 我用 gdb 调试了程序,现在我得到了这个:

Starting program: /home/saroj/workspace/Rachaita3D/Rachaita3D step
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff5120700 (LWP 11819)]

Thread 1 "Rachaita3D" received signal SIGSEGV, Segmentation fault.
0x0000555555565f66 in __gnu_cxx::new_allocator<glm::vec<2, float, (glm::qualifier)0> >::construct<glm::vec<2, float, (glm::qualifier)0>, glm::vec<2, float, (glm::qualifier)0> const&> (this=0x7fffffffdca0, __p=0x555555950a50, __args#0=...)                              
    at /usr/include/c++/8/ext/new_allocator.h:136
136             { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }

编辑 2: 在tex.push_back(ttex[vertex - 1]);,p ttex[vertex - 1] 返回

Cannot access memory at address 0x0

由于某种原因,我的加载器没有加载纹理坐标和法线向量。

【问题讨论】:

  • 我在这里看到了一些导致未定义行为的潜在原因,由于缺乏边界检查,但没有什么可以证明是错误的。很可能存在导致现有代码中其他地方的内存损坏的错误,直到添加此新代码导致行为发生变化时才检测到该错误。您将需要使用静态分析工具和/或调试器来调试它。将 coredump 加载到调试器中可能会提供一些线索。
  • @SamVarshavchik 我用 gdb 日志更新了帖子。
  • 您可以从 gdb 获得更多信息,然后。例如回溯,以及每个堆栈帧的调用站点上的所有变量的值。听起来你需要多花一点时间学习如何从 gdb 中获取有用的信息,以便能够调试复杂的 C++ 代码。
  • Edit 2 可能会有所帮助,因为我不明白为什么 ttex[vertex - 1] 位于 0x0。
  • 在 gdb 中输入“bt”(然后按回车键)以获取更多信息。

标签: c++ opengl segmentation-fault glm-math wavefront


【解决方案1】:

现在的问题在于您解析 Wavefront OBJ 命令的方式。

第一次检查strncmp(start, "v", strlen("v")) 不检查命令是否为v,它检查命令是否以“v”开头

当代码遇到vnvt时,第一次检查会发现'v'开头,并走错分支。您用不相关的数据填充 tvert 数组,将 tnormttex 留空,因此代码在尝试索引它们时崩溃。

要么将字符串与strcmp() 进行比较,要么重新排序检查,因此v 将被最后测试。

【讨论】:

    【解决方案2】:

    你对数据做错了什么。你通过Wavefront OBJ specification了吗?

    您的代码希望加载的实际 3D 模型具有非常精确的结构,而 OBJ 格式允许面部元素的非常灵活的组合。

    OBJ 文件不仅可以支持三角形,还可以支持由 4 个或更多顶点组成的多边形(全平面)。您不应该在任何地方对数字 3 进行硬编码,但也许您可以跳过/忽略由 2 个顶点组成的“线”。

    数据向量的大小可能与 3 不同。通常,顶点坐标是 2 个分量,而不是 3 个,但有时您会看到 3 个,甚至 4 个分量。

    每个面指定其顶点的组件索引,f 命令中的索引以多个变体给出 - 作为单个数字,仅指定位置。作为由/ 斜线分隔的一对索引,指定位置和纹理坐标。作为索引的三元组,指定 position/texture_coords/normal_vector。当模型具有没有纹理坐标的位置和法线数据时,您会看到纹理坐标完全缺失的三元组,例如 1//2。您必须跟踪这些差异,因为某些数据会完全丢失,并且对其进行索引会触发段错误。

    另一个特点是索引本身。它们可能是负数,不是指定数据数组中的位置,而是指定组件向量累积数组中当前位置的偏移量。

    因此,您的代码至少有两个问题肯定会导致段错误,具体取决于正在加载的确切 3D 文件。

    1. 您不检查f 命令中是否存在顶点分量索引(纹理坐标或法线),因此读取该索引将失败,给出垃圾或未初始化的零。在执行ttex[vertex - 1] 时,您将从该零中减去 1,并且您正在读取数据数组之外的内容。

    2. 您不检查索引是否为负。因此,您将再次读取数据数组之外的内容。

    【讨论】:

    猜你喜欢
    • 2012-03-23
    • 1970-01-01
    • 2014-08-31
    • 2023-03-10
    • 1970-01-01
    • 2017-10-05
    • 2019-10-27
    • 1970-01-01
    • 2021-08-08
    相关资源
    最近更新 更多