2

За последние пару часов я пытался отследить ошибку в моей программе, которая возникает только при запуске в режиме выпуска. Я уже разрешил все предупреждения-компиляторы уровня 4, и нет никаких неинициализированных переменных в любом месте (что обычно было бы моим первым подозреваемым в таком случае).Оптимизация компилятора разрывает код

Это сложно объяснить, так как я даже точно не знаю, что именно происходит, так что медведь со мной, пожалуйста.

После много отладки, я сузили причину ошибки вниз куда-то в следующей функции:

void CModelSubMesh::Update() 
{ 
    ModelSubMesh::Update(); 

    auto bHasAlphas = (GetAlphaCount() > 0) ? true : false; 
    auto bAnimated = (!m_vertexWeights.empty() || !m_weightBoneIDs.empty()) ? true : false; 
    if(bHasAlphas == false && bAnimated == false) 
     m_glMeshData = std::make_unique<GLMeshData>(m_vertices,m_normals,m_uvs,m_triangles); 
    else 
    { 
     m_glmesh = GLMesh(); 
     auto bufVertex = OpenGL::GenerateBuffer(); 
     auto bufUV = OpenGL::GenerateBuffer(); 
     auto bufNormal = OpenGL::GenerateBuffer(); 
     auto bufIndices = OpenGL::GenerateBuffer(); 
     auto bufAlphas = 0; 
     if(bHasAlphas == true) 
      bufAlphas = OpenGL::GenerateBuffer(); 

     auto vao = OpenGL::GenerateVertexArray(); 

     m_glmesh.SetVertexArrayObject(vao); 
     m_glmesh.SetVertexBuffer(bufVertex); 
     m_glmesh.SetUVBuffer(bufUV); 
     m_glmesh.SetNormalBuffer(bufNormal); 
     if(bHasAlphas == true) 
      m_glmesh.SetAlphaBuffer(bufAlphas); 
     m_glmesh.SetIndexBuffer(bufIndices); 
     m_glmesh.SetVertexCount(CUInt32(m_vertices.size())); 
     auto numTriangles = CUInt32(m_triangles.size()); // CUInt32 is equivalent to static_cast<unsigned int> 
     m_glmesh.SetTriangleCount(numTriangles); 
     // PLACEHOLDER LINE 

     OpenGL::BindVertexArray(vao); 

     OpenGL::BindBuffer(bufVertex,GL_ARRAY_BUFFER); 
     OpenGL::BindBufferData(CInt32(m_vertices.size()) *sizeof(glm::vec3),&m_vertices[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

     OpenGL::EnableVertexAttribArray(SHADER_VERTEX_BUFFER_LOCATION); 
     OpenGL::SetVertexAttribData(
      SHADER_VERTEX_BUFFER_LOCATION, 
      3, 
      GL_FLOAT, 
      GL_FALSE, 
      (void*)0 
     ); 

     OpenGL::BindBuffer(bufUV,GL_ARRAY_BUFFER); 
     OpenGL::BindBufferData(CInt32(m_uvs.size()) *sizeof(glm::vec2),&m_uvs[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

     OpenGL::EnableVertexAttribArray(SHADER_UV_BUFFER_LOCATION); 
     OpenGL::SetVertexAttribData(
      SHADER_UV_BUFFER_LOCATION, 
      2, 
      GL_FLOAT, 
      GL_FALSE, 
      (void*)0 
     ); 

     OpenGL::BindBuffer(bufNormal,GL_ARRAY_BUFFER); 
     OpenGL::BindBufferData(CInt32(m_normals.size()) *sizeof(glm::vec3),&m_normals[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

     OpenGL::EnableVertexAttribArray(SHADER_NORMAL_BUFFER_LOCATION); 
     OpenGL::SetVertexAttribData(
      SHADER_NORMAL_BUFFER_LOCATION, 
      3, 
      GL_FLOAT, 
      GL_FALSE, 
      (void*)0 
     ); 

     if(!m_vertexWeights.empty()) 
     { 
      m_bufVertWeights.bufWeights = OpenGL::GenerateBuffer(); 
      OpenGL::BindBuffer(m_bufVertWeights.bufWeights,GL_ARRAY_BUFFER); 
      OpenGL::BindBufferData(CInt32(m_vertexWeights.size()) *sizeof(float),&m_vertexWeights[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

      OpenGL::EnableVertexAttribArray(SHADER_BONE_WEIGHT_LOCATION); 
      OpenGL::BindBuffer(m_bufVertWeights.bufWeights,GL_ARRAY_BUFFER); 
      OpenGL::SetVertexAttribData(
       SHADER_BONE_WEIGHT_LOCATION, 
       4, 
       GL_FLOAT, 
       GL_FALSE, 
       (void*)0 
      ); 
     } 
     if(!m_weightBoneIDs.empty()) 
     { 
      m_bufVertWeights.bufBoneIDs = OpenGL::GenerateBuffer(); 
      OpenGL::BindBuffer(m_bufVertWeights.bufBoneIDs,GL_ARRAY_BUFFER); 
      OpenGL::BindBufferData(CInt32(m_weightBoneIDs.size()) *sizeof(int),&m_weightBoneIDs[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

      OpenGL::EnableVertexAttribArray(SHADER_BONE_WEIGHT_ID_LOCATION); 
      OpenGL::BindBuffer(m_bufVertWeights.bufBoneIDs,GL_ARRAY_BUFFER); 
      glVertexAttribIPointer(
       SHADER_BONE_WEIGHT_ID_LOCATION, 
       4, 
       GL_INT, 
       0, 
       (void*)0 
      ); 
     } 

     if(bHasAlphas == true) 
     { 
      OpenGL::BindBuffer(bufAlphas,GL_ARRAY_BUFFER); 
      OpenGL::BindBufferData(CInt32(m_alphas.size()) *sizeof(glm::vec2),&m_alphas[0],GL_STATIC_DRAW,GL_ARRAY_BUFFER); 

      OpenGL::EnableVertexAttribArray(SHADER_USER_BUFFER1_LOCATION); 
      OpenGL::SetVertexAttribData(
       SHADER_USER_BUFFER1_LOCATION, 
       2, 
       GL_FLOAT, 
       GL_FALSE, 
       (void*)0 
      ); 
     } 
     OpenGL::BindBuffer(bufIndices,GL_ELEMENT_ARRAY_BUFFER); 
     OpenGL::BindBufferData(numTriangles *sizeof(unsigned int),&m_triangles[0],GL_STATIC_DRAW,GL_ELEMENT_ARRAY_BUFFER); 

     OpenGL::BindVertexArray(0); 
     OpenGL::BindBuffer(0,GL_ARRAY_BUFFER); 
     OpenGL::BindBuffer(0,GL_ELEMENT_ARRAY_BUFFER); 

    } 
    ComputeTangentBasis(m_vertices,m_uvs,m_normals,m_triangles); 
} 

Моя программа представляет собой графическое приложение, и этот фрагмент кода создает объект буфера которые необходимы для рендеринга позже. Ошибка в основном приводит к тому, что вершины конкретной сетки отображаются некорректно при выполнении определенных условий. Ошибка является последовательной и происходит каждый раз для той же сетки.

К сожалению, я не могу сузить код до конца, поскольку это приведет к исчезновению ошибки и объяснению того, что каждая строка будет занимать довольно много времени, и здесь не слишком уместна. Я почти уверен, что это проблема с оптимизацией компилятора, поэтому фактическая ошибка в этом случае скорее является побочным эффектом.

С кодом выше ошибка будет происходить, но только в режиме деблокирования. Интересная часть - это строка, которую я обозначил как «PLACEHOLDER LINE».

Если изменить код к одному из следующих 3-х вариантов, то ошибка исчезает:

# 1:

void CModelSubMesh::Update() 
{ 
    [...] 
    // PLACEHOLDER LINE 
    std::cout<<numTriangles<<std::endl; 
    [...] 
} 

# 2:

#pragma optimize("", off) 
void CModelSubMesh::Update() 
{ 
    [...] // No changes to the code 
} 
#pragma optimize("", on) 

# 3:

static void test() 
{ 
    auto *f = new float; // Do something to make sure the compiler doesn't optimize this function away; Doesn't matter what 
    delete f; 
} 
void CModelSubMesh::Update() 
{ 
    [...] 
    // PLACEHOLDER LINE 
    test() 
    [...] 
} 

Специально вариант # 2 указывает, что что-то оптимизировано, чего не должно быть.

Я не ожидаю, что кто-нибудь узнает магию о том, что является корнем проблемы, поскольку для этого потребуется более глубокое знание кода. Однако, может быть, кто-то, кто лучше разбирается в процессе оптимизации компилятора, может дать мне несколько советов, что может сделать ?

Поскольку почти любое изменение кода избавляется от ошибки, я просто не уверен, что я могу сделать, чтобы найти причину этого.

+4

Я 99.9 (9)% уверен, что компилятор здесь НЕ виноват. Ошибки, которые возникают только в релизе, обычно связаны с памятью или связаны с несколькими потоками. И ошибка не исчезает, когда вы делаете изменения кода, которые вы описали, они просто скрываются. I. e. вы этого не замечаете, но измените что-то еще, и оно снова станет обнаженным. –

+1

Если это повреждение памяти, оно может быть где угодно в вашем приложении, а не в фрагменте кода, который вы разместили здесь. –

+0

Если это повреждение памяти, оно не объясняет, почему оно работает с 3 вариантами, которые я предоставил. Без одного из вариантов ошибка возникает 10 из 10 раз. С одним из вариантов на месте это происходит 0 из 10 раз. – Silverlan

ответ

2

Чаще всего, когда я ударил что-то, что работает в отладке, но не в выпуске, это неинициализированная переменная. Большинство компиляторов инициализируют переменные в 0x00 в отладочных сборках, но вы теряете это при включении оптимизаций.

Это может объяснить, почему изменение программы изменяет поведение: путем настройки карты памяти вашего приложения вы получаете некоторый случайный фрагмент неинициализированной памяти, который каким-то образом маскирует проблему.

Если вы сохраняете хорошую гигиеничность управления памятью, вы можете быстро найти проблему с помощью инструмента, такого как valgrind. В долгосрочной перспективе вам может понадобиться изучить структуру управления памятью, которая автоматически обнаруживает нарушения памяти (см. Ogre MemoryTracker, TCMalloc, Clang Memory Sanitizer).

 Смежные вопросы

  • Нет связанных вопросов^_^