最近写毕业设计,想把结果简单的用OpenGL表现一下,结果程序总是莫名其妙的随机crash,在debug模式下,这一次crash在glfw的函数内部,下一次就crash在自己的代码里,再下一次又能好好运行(三次载入的是同一个版本的程序)。

刚开始我是懵逼的,因为有一段时间没写C++了,看到这样的崩溃不知道bug从何找起。而且这次遇到的错误信息貌似和以前都不一样,还有不少干扰信息

我决定先复制一下这个crash的信息,然后google一下看能不能找到原因,结果搜到的都是一些其他的问题产生的bug,和我的情况完全不同,只好作罢。

作为一个OpenGL/WebGL老手,我不是很敢相信是我调用OpenGL API出了问题,因为整个程序比较简单,,我出错的概率比较低。我对照官方的例子和我自己的旧代码检查了几遍,很快排除了opengl一些常见操作导致crash的可能,那么就只剩下自己的代码出问题了。

可是我怎么检查也发现不了问题在哪,于是我只好一个一个注释代码,直到不crash为止

这个工作相当烦,因为怀疑的点太多了!就在我快要抓狂的时候我默念一遍又一遍错误信息,突然灵光一闪:

每一次crash都有错误信息如下:

1
2
3
malloc: *** error for object 0x1001012f8: incorrect checksum for freed object
        - object was probably modified after being freed.
        *** set a breakpoint in malloc_error_break to debug

这里说已经释放的对象的校验和错误,也就是说已释放的对象可能已经被修改

那么什么时候才能修改到已经释放的对象?答案是野指针或者内存越界

想到这其实思路已经差不多出来了,首先我很确信程序中没有野指针,那么就只剩下内存越界了,检查几段数组操作的代码,发现是在操作Mesh的一段代码出了问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 这个函数的功能很简单,利用三角形顶点数据重新计算sharp surface的法线数据
void Mesh::generateSharpNormals() {
  normals.resize(positions.size());
  std::vector<glm::vec3> flat_positions;
  std::vector<glm::vec3> flat_normals;
  std::vector<unsigned int> flat_indices;
  for (unsigned int i = 0; i < indices.size() / 3; ++i) {
    // 这里直接把i当做index去访问顶点数组,因此导致越界
    glm::vec3 u = positions[i * 3] - positions[i * 3 + 1];
    glm::vec3 v = positions[i * 3 + 1] - positions[i * 3 + 2];
    glm::vec3 normal = glm::normalize(glm::cross(v, u));
    for (unsigned int j = 0; j < 3; ++j) {
      flat_normals.push_back(normal);
      flat_positions.push_back(positions[i * 3 + j]);
      flat_indices.push_back(3 * i + j);
    }
  }
  positions = flat_positions;
  normals = flat_normals;
  indices = flat_indices;
}

这段代码越界访问了堆区内存(访问的是vector内部的c array),导致后续的new校验失败,也就是说可能引起毫不相关代码的crash(但也不是完全不相关,被crash的代码应该紧接着这个越界操作不久,时间上局部性比较大)非常干扰程序员的判断

下面是正确的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void Mesh::generateSharpNormals() {
  normals.resize(positions.size());
  std::vector<glm::vec3> flat_positions;
  std::vector<glm::vec3> flat_normals;
  std::vector<unsigned int> flat_indices;
  for (unsigned int i = 0; i < indices.size() / 3; ++i) {
    glm::vec3 u = positions[indices[i * 3]] - positions[indices[i * 3 + 1]];
    glm::vec3 v = positions[indices[i * 3 + 1]] - positions[indices[i * 3 + 2]];
    glm::vec3 normal = glm::normalize(glm::cross(v, u));
    for (unsigned int j = 0; j < 3; ++j) {
      // 这里扩充了整个VBO
      flat_normals.push_back(normal);
      flat_positions.push_back(positions[indices[i * 3 + j]]);
      flat_indices.push_back(3 * i + j);
    }
  }
  positions = flat_positions;
  normals = flat_normals;
  indices = flat_indices;
}

这真的是非常低级的一个错误,不仅导致了程序崩溃,也会导致法线数据混乱。

需要注意的是,msvc 的debug mode会在vector的operator[] 检查越界,然而如果在mac使用的是libc++却不会

另外,以后再遇到incorrect checksum for freed object 这样的报错信息大概就知道是怎么回事了吧。。。不过出内存问题应该首先想到跑静态分析,能省下很多痛苦的debug时间