Оптимизация компилятора нарушает код

Последние пару часов я пытался отследить ошибку в моей программе, которая возникает только при запуске в режиме релиза. Я уже разрешил все предупреждения компилятора уровня 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 указывает, что что-то оптимизируется, чего не должно быть.

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

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

1 ответ

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

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

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

Другие вопросы по тегам