创建第二个 object 后堆损坏

Heap corruption after creation of 2nd object

我正在 VS 中使用 OpenGL 构建贪吃蛇游戏。我有我的 class 处理模型,VertexData,然后 Main 处理游戏的其余部分。每当我通过调用 createModel() 创建第一个 object 时,一切正常。但是每当我第二次调用 createModel() 时,比如创建苹果,Visual Studio 有大约 40% 的机会触发断点。每次我在那之后调用 createModel(),比如创建另一个 body 段,它触发断点的机会就会增加。是因为我在哪里创建 object,还是有其他问题?我看到向 VertexData class 添加一个复制构造函数会阻止崩溃,但事实并非如此。

Main.cpp:

//includes
#include "convertToFloat.h"
#include "vertexData.h"
#include <iostream>
#include <vector> 
#include <time.h>

//function prototypes
static void error_callback(int error, const char* description);
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void initWindow();
void destroy();
void changeLocation();
void update();
void render();
void loadModels();
void onStartUp();
void onCollect();
void createModel();
int roundUp(int numToRound, int multiple);
int roundDown(int numToRound, int multiple);

//object declerations
GLFWwindow* window;

//variables
int x = 200;
int y = 200;
int appleLoc[2] = { x,y };
int direction = 0;
int stepSize = 20;
bool start = false;
static double limitFPS = 1.0 / 15.0;
double lastTime = glfwGetTime(), timer = lastTime;
double deltaTime = 0, nowTime = 0;
int frames = 0, updates = 0;
std::vector<std::shared_ptr<VertexData>> models;

int main(void)
{
    initWindow();
    loadModels();
    onStartUp();
    while (!glfwWindowShouldClose(window))
    {
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        nowTime = glfwGetTime();
        deltaTime += (nowTime - lastTime) / limitFPS;
        lastTime = nowTime;
        while (deltaTime >= 1.0) {
            update();
            updates++;
            deltaTime--;
        }
        render();
        frames++;

        if (glfwGetTime() - timer > 1.0) {
            timer++;
            updates = 0, frames = 0;
        }
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    destroy();
}

void onCollect() {
    appleLoc[0] = roundUp(rand() % 620, 20);
    appleLoc[1] = roundUp(rand() % 460, 20);
    //models.at(1)->move(appleLoc[0], appleLoc[1]);
    //createModel();
}

void onStartUp() {
    srand(time(0));
    onCollect();
}

void createModel() {
    std::shared_ptr<VertexData> model{ new VertexData("models/snakeHead.md",640,480) };
    models.push_back(model);
}

void loadModels() {
    createModel();
    createModel();
}

void changeLocation() {
    switch (direction) {
        case(0):
            if(y<460)
                y += stepSize;
            break;
        case(1):
            if (x < 620)
                x += stepSize;
            break;
        case(2):
            if (y > 0)
                y -= stepSize;
            break;
        case(3):
            if (x > 0)
                x -= stepSize;
            break;
    }
    std::cout << x << " " << y << std::endl;
    std::cout << appleLoc[0] << " " << appleLoc[1] << std::endl;
}

void render() {
    for(int i=0; i<models.size();i++)
        models.at(i)->render();
}

void update() {
    if (start)
        changeLocation();
    models.at(0)->move(x, y);
    if (x == appleLoc[0] && y == appleLoc[1]) {
        onCollect();
    }
}

void initWindow() {
    if (!glfwInit())
        exit(EXIT_FAILURE);

    window = glfwCreateWindow(640, 480, "Snek", NULL, NULL);

    if (!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    glfwMakeContextCurrent(window);
    gladLoadGL(glfwGetProcAddress); //important
    glfwSwapInterval(1);

    glfwSetErrorCallback(error_callback);
    glfwSetKeyCallback(window, key_callback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
}

static void error_callback(int error, const char* description)
{
    fprintf(stderr, "Error: %s\n", description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GLFW_TRUE);
    if (key == GLFW_KEY_W && action == GLFW_PRESS) {
        direction = 0;
        start = true;
    }
    if (key == GLFW_KEY_S && action == GLFW_PRESS){
        direction = 2;
        start = true;
    }
    if (key == GLFW_KEY_A && action == GLFW_PRESS){
        direction = 3;
        start = true;
    }
    if (key == GLFW_KEY_D && action == GLFW_PRESS){
        direction = 1;
        start = true;
    }
}

int roundUp(int numToRound, int multiple)
{
    if (multiple == 0)
        return numToRound;

    int remainder = numToRound % multiple;
    if (remainder == 0)
        return numToRound;

    return numToRound + multiple - remainder;
}

int roundDown(int numToRound, int multiple)
{
    if (multiple == 0)
        return numToRound;

    int remainderInverseSorta = multiple-(numToRound % multiple);
    if (remainderInverseSorta == 0)
        return numToRound;

    return numToRound - multiple + remainderInverseSorta;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

void destroy() {
    for (int i = 0; i < models.size(); i++)
        models.at(i)->destroy();
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

vertexdata.h:

#ifndef vertextData
#define vertexData

#define GLFW_INCLUDE_NONE
#include "loadFile.h"
#include "convertToFloat.h"
#include "shaderLoader.h"
#include <GLFW/glfw3.h>
#include <glad/gl.h> // include glad to get all the required OpenGL headers
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


class VertexData {
    private:
        unsigned int VAO,VBO,EBO;
        int width = 0;
        int height = 0;

        std::unique_ptr <Shader> shader{ new Shader("Shaders/3.3.shader.vs", "Shaders/3.3.shader.fs") }; //add shader path to constructor
        glm::mat4 trans = glm::mat4(1.0f);
    public:
        VertexData(const char* modelPath,int width,int height);
        VertexData(const VertexData& data);
        void render();
        void move(int x, int y);
        void rotate(int deg);
        void destroy();
};
#endif

vertexData.cpp:

#include "vertexData.h"

VertexData::VertexData(const char* modelPath, int width, int height) {
    this->width = width;
    this->height = height;
    std::unique_ptr<ConvertToFloat> conversion{ new ConvertToFloat(width, height) };
    std::unique_ptr<LoadFile> file{ new LoadFile() };
    std::stringstream modelStream;
    std::string substr;
    modelStream = file->load(modelPath);
    std::getline(modelStream, substr, ','); 
    int numVertices = stoi(substr);
    float* vertices = new float[numVertices*8];
    std::getline(modelStream, substr, '\n');
    int numIndices = stoi(substr);
    int* indices = new int[numIndices];
    int step = 0;
    for (int i = 0; i < numVertices * 8; i++) {
        if(step!=7)
            std::getline(modelStream, substr, ',');
        else
            std::getline(modelStream, substr, '\n');
        vertices[i] = stof(substr);
        if (step == 7)
            step = 0;
        else
            step++;
    }
    step = 0;
    for (int i = 0; i < numIndices; i++) {
        if (step == 2) {
            std::getline(modelStream, substr, '\n');
            step = 0;
        }
        else {
            std::getline(modelStream, substr, ',');
            step++;
        }
        indices[i] = stoi(substr);
    }
    
    conversion->format(vertices, numVertices * 8 * sizeof(float));
    //binds id
    glGenBuffers(1, &VBO);
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &EBO);


    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, numVertices*8*sizeof(float), vertices, GL_STATIC_DRAW);
    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*4, indices, GL_STATIC_DRAW);
    //texture
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

VertexData::VertexData(const VertexData& data) {
    VAO = data.VAO;
    VBO = data.VBO;
    EBO = data.EBO;
    width = data.width;
    height = data.height;
    trans = data.trans;
}

void VertexData::render() {
    shader->use();
    unsigned int transformLoc = glGetUniformLocation(shader->ID, "location");
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));


    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

void VertexData::move(int x, int y) {
    float coor[2] = { float(x),float(y) };
    std::unique_ptr<ConvertToFloat> conversion{ new ConvertToFloat(width,height) };
    conversion->convertToGlobal(coor);
    glm::mat4 temp = glm::mat4(1.0f);
    temp = glm::translate(temp, glm::vec3(coor[0],coor[1], 0.0f));
    trans = temp;
}

void VertexData::rotate(int deg) {

}

void VertexData::destroy() {
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);
}

loadFile.h:

#pragma once
#ifndef loadFileH
#define loadFileH

#include <fstream>
#include <sstream>
#include <iostream>

class LoadFile {
    private:
    public:
        LoadFile() {}
        std::stringstream load(const char* path) {
            std::ifstream file;
            std::stringstream stream;
            file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
            try {
                file.open(path);
                stream << file.rdbuf();
                // close file handlers
                file.close();
                return stream;
            }
            catch (std::ifstream::failure e)
            {
                std::cout << "ERROR::FILE_NOT_SUCCESFULLY_READ" << std::endl;
                return stream;
            }
        }
};
#endif

shaderLoader.h:

#ifndef SHADER_H
#define SHADER_H

#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad/gl.h> // include glad to get all the required OpenGL headers
#include "loadFile.h"


#include <string>


class Shader
{
public:
    // the program ID
    unsigned int ID;

    // constructor reads and builds the shader
    Shader(const char* vertexPath, const char* fragmentPath) {
        std::unique_ptr<LoadFile> fragFile{ new LoadFile() };
        std::unique_ptr<LoadFile> vertexFile{ new LoadFile() };
        std::string vertexCode;
        std::string fragmentCode;
        vertexCode = vertexFile->load(vertexPath).str();
        fragmentCode = fragFile->load(fragmentPath).str();
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();

        // 2. compile shaders
        unsigned int vertex, fragment;
        int success;
        char infoLog[512];

        // vertex Shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        // print compile errors if any
        glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(vertex, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
        };

        // similiar for Fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        // print compile errors if any
        glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(fragment, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
        };

        // shader Program
        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        // print linking errors if any
        glGetProgramiv(ID, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(ID, 512, NULL, infoLog);
            std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
        }

        // delete the shaders as they're linked into our program now and no longer necessary
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // use/activate the shader
    void use(){
        glUseProgram(ID);
    }
};
#endif

根据VS触发断点的区域为:loadFile.h第19行stream << file.rdbuf();、shaderLoader.h第27行const char* vShaderCode = vertexCode.c_str();、[=75行vertexData.cpp } 这只是一个右括号。 如果我在断点后单击继续,我会收到错误 Unhandled exception at 0x7727FA1D (ntdll.dll) in snek.exe: 0xC0000374: A heap has been corrupted (parameters: 0x772BB960).

编辑: convertToFloat.h:

#ifndef convertToFloat
#define convertToFloat

class ConvertToFloat {
    public:
        ConvertToFloat(int width, int height);
        ConvertToFloat();
        void convertToGlobal(float* input);
        void convertFromRGB(float* input, const int size);
        void format(float* input, const int size);
    private:
        int width = 0;
        int height = 0;
};
#endif

convertToFloat.cpp

#include "convertToFloat.h"

ConvertToFloat::ConvertToFloat(int width, int height) {
    this->width = width;
    this->height = height;
}

ConvertToFloat::ConvertToFloat() {

}

void ConvertToFloat::convertToGlobal(float* input) {
    input[0] = 2.0*input[0] / width;
    input[1] = 2.0*input[1] / height;
}

void ConvertToFloat::convertFromRGB(float* input, const int size) {
    for (int i = 0; i < size; i++)
        input[i] = input[i] / 255;
}

void ConvertToFloat::format(float* input, const int size) {
    int step = 0;
    for (int i = 0; i < size; i++) {
        if (step < 3) {
            if (step == 1)
                input[i] = ((input[i] * 2) / height) - 1;
            else
                input[i] = ((input[i] * 2) / width) - 1;
        }
        else if (step < 6) {
            input[i] = input[i] / 255;
        }
        if (step == 7)
            step = 0;
        else
            step++;
    }
}

假设 ConvertToFloat::format 有一个指针和一个长度,这就是你的问题:

conversion->format(vertices, numVertices * 8 * sizeof(float));

vertices 只有 numVertices * 8 个元素,您要将其乘以 sizeof(float)。因此该函数将愉快地破坏 vertices 缓冲区后的大量内存。

立即修复是微不足道的:失去 * sizeof(float),但我恳请您停止使用原始内存和指针并拥抱 std::vector

如果您声明 std::vector<float> vertices,您只需将其传递给 ConvertToFloat::format,它会自动 a) 知道大小和 b) 如果您进行越界访问,则发出警报。

您始终可以通过调用 vertices.data().

获取指向后备数组的指针

此外,如果您定义一个

struct Vertex { float position[3]; float color[3]; float texcoord[2]; }

您可以记录您的 VBO 格式并大大清理您的属性分配代码:

glBufferData(GL_ARRAY_BUFFER, vertices.size()*sizeof(float), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texcoord));
glEnableVertexAttribArray(2);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size()*sizeof(int), indices.data(), GL_STATIC_DRAW);

理想情况下 vertices 只是一个 std::vector<Vertex>,但我可以想象它不会立即适合您的加载代码。