【问题标题】:Access Violation reading elements of an array访问冲突读取数组的元素
【发布时间】:2011-03-04 04:24:21
【问题描述】:

我已经编写了自己的代码来解析 .obj 模型文件 - 基本上只是 ASCII 文本。根据我的测试,该文件被正确解析并存储在类中。我可以在加载函数中很好地读取值(来自数据成员)。

当我尝试读回主渲染循环中的值时会出现问题。以“int v”开头的行出现访问冲突错误:

 for(int i = 0; i<data.numFaces; i++){
  for(int j = 0; j<3; j++){ //Assuming triangles for now.

   int v = data.faceList[i].vertex[j]; // Access violation here.
   double vX = data.vertexList[v].x;
   double vY = data.vertexList[v].y;
   double vZ = data.vertexList[v].z;
   glVertex3d(vX, vY, vZ);
  }
 }

我不确定为什么会发生这种情况,并且我已经检查了我可能想到的所有内容。我在 C++ 方面不是很有经验。尽管我之前用 C++ 编写过一个中等规模的项目,但我的大部分编程经验是 Java、Python 和 PHP。

我确定问题与内存分配或用于动态数组的指针有关。

这里是obj加载类的相关代码:

ObjData ObjLoader::LoadObj(std::string filename){

    //... Initalization ...

 // 1st pass: find number of elements so array sizes can be defined.
 while(!file.eof()){
  //...
 }

 //...close file...

 _data.faceList = new ObjFace[_data.numFaces];
 _data.vertexList = new ObjVert[_data.numVertices];
 _data.uvList = new ObjUV[_data.numUVcoords];
 _data.normalList = new ObjNormal[_data.numNormals];

    //TODO: Make size dynamic according to each face. Just use the first 3 points for now.
 for (int i = 0; i < _data.numFaces; i++){
  _data.faceList[i].vertex = new int[3];
  _data.faceList[i].normal = new int[3];
  _data.faceList[i].uv = new int[3];
 }

 //... file stuff ...

 // 2nd pass: read values into arrays.
 while(!file.eof()){
  //...

  if(type=="v"){
   _data.vertexList[currentVertex].x = atof(param1.c_str());
   _data.vertexList[currentVertex].y = atof(param2.c_str());
   _data.vertexList[currentVertex].z = atof(param3.c_str());
   currentVertex++;
  }else if(type=="vt"){
   _data.uvList[currentUV].u = atof(param1.c_str());
   _data.uvList[currentUV].v = atof(param2.c_str());
   currentUV++;
  }else if(type=="vn"){
   _data.normalList[currentNormal].x = atof(param1.c_str());
   _data.normalList[currentNormal].y = atof(param2.c_str());
   _data.normalList[currentNormal].z = atof(param3.c_str());
   currentNormal++;
  }else if(type=="f"){
  //...Within loop over each vertex in a single face ...

        if(endPos != string::npos){
        // Value before 1st "/" (Vertex index).
        // ...find value in string...
        _data.faceList[currentFace].vertex[i] = atoi(token.c_str()) -1; // File format begins indices from 1.

        // Value between slashes (UV index).
        // ...find value in string...
        _data.faceList[currentFace].uv[i] = atoi(token.c_str()) -1;

        // Value after 2nd "/" (Normal index).
        // ...find value in string...
        _data.faceList[currentFace].normal[i] = atoi(token.c_str()) -1;
   }
//...End of loop over every vertex in a single face...
currentFace++;
}

}

 return _data;

    }

而结构体 ObjFace、ObjVert、ObjUV 和 ObjNormal 定义为:

    struct ObjVert{
       float x, y, z;
    };

    struct ObjUV{
      float u, v;
    };

    struct ObjNormal{
       float x, y, z;
    };

    // Contains indexes.
       struct ObjFace{
       int* vertex;
       int* uv;
       int* normal;
    };

感谢您的帮助。此外,任何有关在未来避免此类错误的良好资源都将不胜感激。

【问题讨论】:

  • 您能否添加更多相关代码?比如_data是什么类型?另外,在上面的代码中,data 是否与后面的代码中的_data 相同,或者是否有我们没有看到的赋值?在我看来,我们被要求对您的代码做出太多假设。
  • 哦,对了,我尽量删除不相关的代码。顶部的 sn-p 在我的主渲染循环中,就在此之前,调用了 LoadObj(即较低的 2 个 sn-ps),其中返回的值被分配给数据,如下所示: data = LoaderClass.LoadObj("立方体.obj"); _data 和 data 都是 ObjData 类型,存储加载的 .obj。
  • 虽然问题(有些)不同,但您可能想查看我之前发布的有关读取 obj 文件的答案中的代码:stackoverflow.com/questions/2908854/…
  • @usm:我在看到你的评论之前写了一个答案,但你似乎证实了我对你的作业的期望。我完全承认我不是这个确切主题的专家,但我相信我的回答应该能帮助你到达你需要去的地方。作为对此的快速测试,您可以在 LoadObj 方法中使用指针地址编写输出,然后在分配后再次在 data 对象上编写输出。我怀疑你会发现它们不同步。

标签: c++ arrays multidimensional-array access-violation


【解决方案1】:

我输入了一些愚蠢的回复,我意识到这是不对的......但我不得不继续思考它,我想出了另一个想法。

这个对象被分配到哪里了?

您的代码不清楚data_data 是否是同一个对象,但我注意到您的方法似乎将_data 作为对象返回。我被引导相信,也许您正在某处使用像 ObjData data = LoadObj("myfilename"); 这样的作业?

如果是这种情况,我相信您的问题可能来自您的 ObjData 类缺少复制构造函数或重载赋值运算符。 (我不是 C++ 专家,所以我不记得到底属于哪一个。希望其他人可以确认我们是否走在正确的轨道上)。

如果您的指针在复制和赋值期间没有被正确复制(从LoadObj 返回调用复制构造函数iirc,然后明显分配给data),那么即使您打算已经有一个数组int 在该位置,您实际上可能正在访问未初始化的内存,从而导致您的访问冲突。

我不是复制构造函数或重载赋值运算符的专家,但解决此问题的快速方法是返回指向 ObjData 的指针,而不是返回对象本身。

【讨论】:

  • 我认为你是对的。我没有 ObjData 类的复制构造函数或赋值运算符。做一些阅读告诉我它们是具有指针属性的对象所必需的。这也解释了为什么一切都在 LoadObj 函数中起作用,而不是在它之外。在 LoadObj 之外,原始数据的析构函数被调用,并且副本仍然指向相同的内存部分,现在是无效的。明天早上我会写一个拷贝构造函数和赋值运算符,然后回来报告。谢谢!
  • 我写了一个拷贝构造函数,重载了赋值运算符,问题就解决了。非常感谢!
【解决方案2】:

乍一看,我没有发现任何明显的错误。但是,如果如您所说,它在int v = data.faceList[i].vertex[j]; 处爆炸,那么问题很可能是ij 或两者都太大或太小。

除了熟悉调试器并消除此错误的明显方法之外,解决此类问题的最佳方法可能是完全避免它们。程序员做的某些事情比其他事情更容易出错。清单很长,但您至少要在代码中执行其中两个。

1) 您使用动态分配的数组 2)你使用手工制作的循环

尽量避免使用 C++ 提供的工具来做这些事情。从 #1 开始,摆脱动态分配的数组。

你有一个结构:

struct ObjFace{
  int* vertex;
  int* uv;
  int* normal;
};

...带有 3 个指向int 数组的指针。而不是这样做,使用vector

struct ObjFace{
  vector<int> vertex;
  vector<int> uv;
  vector<int> normal;
};

...然后您之前必须编写的大量代码现在变得更加简单,并且更不容易出错:

// all this goes away
//_data.faceList = new ObjFace[_data.numFaces];
//_data.vertexList = new ObjVert[_data.numVertices];
//_data.uvList = new ObjUV[_data.numUVcoords];
//_data.normalList = new ObjNormal[_data.numNormals];

...和:

// now you ask the vector how many elements it really has
for(int i = 0; i<data.faceList.size(); i++){
  for(int j = 0; j<data.faceList.size(); j++){ //Ask the vector instead of assuming triangles

   int v = data.faceList[i].vertex[j]; // Access violation here.
   double vX = data.vertexList[v].x;
   double vY = data.vertexList[v].y;
   double vZ = data.vertexList[v].z;
   glVertex3d(vX, vY, vZ);
  }
 }

现在,看看那个循环。循环是一个非常常见的错误来源。最好的循环是你永远不必编写的循环。所以请改用 STL 的算法。向 ObjFace 添加一个函数以在其每个元素上执行 glVertex3d

struct ObjFace{
//... 
  void do_vertex() const 
  {
    typedef vector<int> ints;
    for( ints::iterator it = vertex.begin(); it != vertex.end(); ++it )
      glVertex3d(it->x, it->y, it->z);
  }
};

...然后返回并减少原始循环: (伪代码,实际语法更复杂)

typedef vector<ObjFace> ObjFaces;
for( ObjFaces::iterator it = data.faceList.begin(); it != data.faceList.end(); ++it )
  it->do_vertex();

...或者,再努力一点:

  for_each( data.faceList.begin(), data.faceList.end(), &ObjFace::do_vertex );

【讨论】:

  • 谢谢,我想我现在大部分事情都会使用向量。我最初避免使用它们是因为我认为它们是基于链表的(我确信它们是在 Java 中的......)但事实似乎并非如此。我也喜欢 ObjFace 中的循环结构。有没有关于如何正确/正确地构建代码的好资源?当然,在这一切之前,我将看看在编写 KevenK 所建议的复制构造函数和赋值运算符之后我现在所拥有的内容是否有效。否则我想我会一直想知道出了什么问题。
  • 因为您说您是 C++ 新手,所以我强烈建议您阅读 Scott Meyers 的 Effective C++ 书籍。他讨论了 C++ 中的很多问题,并就如何安全、清晰和高效地编写 C++ 提供了很多建议。另外,他是一个有趣的作家阅读。事实上,在“Effective C++”第 2 版中,第 11 条说“为具有动态分配内存的类声明一个复制构造函数和一个赋值运算符”。因此,您会在此处看到有关您的确切问题的提示。祝你好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-15
  • 1970-01-01
  • 1970-01-01
  • 2019-09-17
  • 2019-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多