0xC0000005: 写入位置 0x00000000 时发生访问冲突

问题遇到的现象和发生背景

用C语言编写天天酷跑项目,在封装障碍物后,进行初始化和渲染时出现错误

错误信息

0x00007FFDCB111989 (vcruntime140d.dll)处(位于 我的天天酷跑2022.exe 中)引发的异常: 0xC0000005: 写入位置 0x000000000 时发生访问冲突。

代码块
c
/*
* 天天酷跑开发日志
1.创建项目
2.导入素材
3.创建游戏界面(    对于初学者,最好的方式,建议从用户界面入手)
   选择图形库或其他引擎(天天酷跑是基于easyX图形库的)
   1)创建游戏窗口
   2)构思游戏背景
    a.3组背景不同速度同时移动
    b.循环滚动背景实现
   3)实现游戏背景
    a.加载背景资源
    b.渲染/打印图片(背景知识:坐标,以左上角坐标为准)

    第一次运行bug:出现黑色背景
    原因:easyX不支持透明背景的png图片

    2022.12.27

4.实现玩家奔跑
5.实现玩家跳跃
6.创建障碍物小乌龟
7.封装障碍物

学习路径:C语言 C++ 算法(如自动寻路的追心算法)数据库 windows(win32+mfc) Qt linux服务器)


*/
#include 
#include //基于easyX图形库,需安装easyX图形库
#include //判断是否有按键输入的头文件
#include 
#include "tools.h"

using namespace std;//声明命名空间,与vector结合使用

#define WIN_WIDTH 1012
#define WIN_HEIGHT 396
#define OBSTACLE_COUNT 10//定义一个变量表示障碍物总数,进行统一规范管理(修改时只需在此处修改即可修改全局障碍物总数

//保存背景图片
//3类背景图片,用一个数组来存放
//多个函数用到,定义为全局变量
IMAGE imgBgs[3];
int bgSpeed[3] = { 1,2,4 };//数组存放3种背景的移动速度
//背景变化过程中,3张图片X坐标在变化,定义一个数组来存放背景x坐标
int bgX[3];

IMAGE imgHeros[12];//存放玩家图片
int heroX;//玩家遇到障碍物将被弹飞,因此用一个变量来表示其x坐标
int heroY;//玩家会跳跃,用一个变量表示其y坐标
int heroIndex;//显示的玩家图片序号

bool heroJump;//跳跃启动开关
int jumpHeightMax;
//定义偏移量,跳跃改变的单位量
int heroJumpOff;
int update;//表示是否需要马上刷新

//IMAGE imgTortoise;//定义变量存放小乌龟图片
//int torToiseX;//小乌龟x坐标
//int torToiseY;
//bool torToiseExist;//判断当前窗口是否有小乌龟存在,使窗口只出现一个小乌龟

//为了避免障碍物越来越多,代码膨胀,设计结构体把特定类型封装成更加方便的类型
//相当于进行代码优化

//用枚举类型表示类型更加专业可读
typedef enum {
    TORTOISE,//乌龟 0/枚举类型默认首位为0
    LION,//狮子 1
    OBSTACLE_TYPE_COUNT //2加计数变量作为边界,可用于查看枚举元素个数
}obstacle_type;
//obstacle_type type1;障碍物类型变量表示

//vector> data;//尖括号里面表示存放数据类型。需用2维数组,行表示类型,列表示帧数序号

vector> obstacleImgs;//可变数组,存放所有障碍物图片,类似于IMAGE obstacleImgs[3][12]
vector imgTortArray;//定义存放图片的数组


//IMAGE obstacleImgs[3][12];//所有障碍物图片都放在这里,内存消耗的更少
//有的障碍物没有这么多图片,用12张有点浪费

//用c++的容器构造可变数组来实现,需加头文件

typedef struct obstacle {
    obstacle_type type;//障碍物类型
    int imgIndex;//当前显示图片序号
    int x,y;//障碍物坐标
    int speed;
    int power;//杀伤力
    bool exist;//障碍物是否存在
    //IMAGE img[12];//有的障碍物没有12张图片,这种表示方法会浪费很多内存
}obstacle_t;//用于表示一种障碍物

obstacle_t obstacles[OBSTACLE_COUNT];//制作一个障碍物池,对障碍物进行统一管理

void init() {
    //创建游戏窗口
    initgraph(WIN_WIDTH, WIN_HEIGHT);

    
    
    //加载背景资源,加载到内存里面
    char name[64];
    for (int i = 0; i < 3; i++) {
        //"res/bg001.png"
        sprintf(name, "res/bg%03d.png", i + 1);//宽度占3位,不够前面补0
        loadimage(&imgBgs[i], name);//问题解决消除loadimage红色提示

        bgX[i] = 0;//初始化背景x坐标
    }
    //加载玩家奔跑图片素材,加载到内存
    for (int i = 0; i < 12; i++) {
        //"res/hero1.png"...
        sprintf(name, "res/hero%d.png", i + 1);
        loadimage(&imgHeros[i], name);
    }

    //设置玩家初始位置:x坐标为窗口 宽度一半-图片宽度一半。y坐标为图片下边缘y值-图片长度
    heroX = WIN_WIDTH * 0.5 - imgHeros[0].getwidth() * 0.5; 
    heroY = 345 - imgHeros[0].getheight();
    heroIndex = 0;

    heroJump = false;
    jumpHeightMax= 345 - imgHeros[0].getheight()-120;
    heroJumpOff = -4;
    update = true;

    ////加载小乌龟
    //loadimage(&imgTortoise, "res/t1.png");
    //torToiseExist = false;//一开始没有小乌龟
    //torToiseY = 350 - imgTortoise.getheight();
    //封装障碍物后,小乌龟存储位置发生变化,应将小乌龟图片放到二维数组
    IMAGE imgTort;//定义存放小乌龟图片的内存
    loadimage(&imgTort, "res\t1.png");//将路径下的图片到内存
    //vector imgTortArray;//定义存放图片的数组
    imgTortArray.push_back(imgTort);//数组中放入小乌龟图片
    obstacleImgs.push_back(imgTortArray);//将数组添加到二维数组

    IMAGE imgLion;//玩家与背景图片始终存在,定义为全局变量,狮子偶尔出现,定义为局部变量
    vector imgLionArray;
    for (int i = 0; i < 6; i++) {
        sprintf(name, "res/p%d.png", i + 1);//加载狮子
        loadimage(&imgLion, name);
        imgLionArray.push_back(imgLion);
    }
    obstacleImgs.push_back(imgLionArray);

    //对池子进行初始化
    for (int i = 0; i < OBSTACLE_COUNT; i++) {
        obstacles[i].exist = false;
    }

}

void createObstacle() {
    int i;//小技巧,记录找到的不存在的障碍物类型
    for (i = 0; i < OBSTACLE_COUNT; i++) {//0(第一种类型)开始检查障碍物是否存在
        if (obstacles[i].exist == false) {
            break;//障碍物不存在再创建
        }
    }
    if (i >= OBSTACLE_COUNT) {//i >= OBSTACLE_COUNT表示遍历玩障碍物类型,所有障碍物exist都为true,即障碍物都存在,无需创建
        return;
    }
    //下面是找到的情况
    obstacles[i].exist = true;
    obstacles[i].imgIndex = 0;//最开始显示第一张
    obstacles[i].type = (obstacle_type)(rand() * OBSTACLE_TYPE_COUNT);//随机产生一种类型的障碍物//报错,右边rand()*..为int类型,左边type是枚举类型,不能直接转
    //明白层的意思了,代码中的元素分为各种类型,这些类型又以各种类型为属性,以此类推,直到不再细分的底层元素,这其中一种类型就称为一层
    obstacles[i].x = WIN_WIDTH;
    obstacles[i].y = 345 + 5 - obstacleImgs[obstacles[i].type][0].getheight();//创建障碍物首先创建第0张图片,并且位置在初始位置(即窗口最右侧)
    if (obstacles[i].type == TORTOISE) {
        obstacles[i].speed = 0;
        obstacles[i].power = 5;

    }
    else if (obstacles[i].type == LION) {
        obstacles[i].speed = 4;
        obstacles[i].power = 20;
    }
}

//使背景出现坐标移动的变化
void fly() {
    for (int i = 0; i < 3; i++) {
        bgX[i] -= bgSpeed[i];
        //一轮跑完马上切换下一轮/素材背景长度为两个游戏窗口宽度
        if (bgX[i] < -WIN_WIDTH) {
            bgX[i] = 0;
        }
    }
    

    //实现跳跃。在背景下实现跳跃
    if (heroJump) {
        if (heroY <  jumpHeightMax) {//定义一个顶点限制跳跃高度
            heroJumpOff = 4;//高度小于最大高度,偏移量变为正数
        }
        //跳跃过程中每次都加上这个偏移量
        heroY += heroJumpOff;
        if (heroY > 345 - imgHeros[0].getheight()) {
            heroJump = false;//落回地面,跳跃结束,偏移量变为负以便下次跳跃
            heroJumpOff = -4;
        }
    }
    //不跳跃时才改变玩家画面
    else {
        heroIndex = (heroIndex + 1) % 12;//切换玩家图片
    }
    //小乌龟不是一直存在的,需创建小乌龟
        static int frameCount = 0;//普通变量函数每执行一次内存就释放掉,再次调用回到初始值,加static变量永久有效
        //定义一个频度,表示小乌龟出现的间隔帧数
        static int enemyFre = 50;
        frameCount++;//对fly()调用次数计数,每调用100帧(每3s)创建一次小乌龟
        if (frameCount > enemyFre) {
            frameCount = 0;//计数器清零
            //3s创建一个可能导致画面出现多个小乌龟,先判断是否有小乌龟,没有再创建
            //if (!torToiseExist) {
            //    torToiseExist = true;
            //    torToiseX = WIN_WIDTH;
            //    torToiseFre = 200 + rand() % 300;//随机数对300取余表示0-299
            //}
            //进行通用化的障碍物创建
            enemyFre = 50 + rand() % 50;//50-99
            createObstacle();//刷新帧数每大于enemyFre就创建一个障碍物

        }

        //if (torToiseExist) {//小乌龟存在时再对它进行移动
        //    torToiseX -=bgSpeed[2];//小乌龟速度与地面相同,静止出现在地面上
        //    //有始有终,小乌龟跑出画面进行判断
        //    if (torToiseX < -imgTortoise.getheight()) {
        //        torToiseExist = false;
        //    }
        //}
}

//渲染/呈现游戏背景
void updateBg() {


    putimagePNG2(bgX[0], 0, &imgBgs[0]);
    putimagePNG2(bgX[1], 119, &imgBgs[1]);
    putimagePNG2(bgX[2], 330, &imgBgs[2]);

}

void jump() {
    heroJump = true;
    update = true;//按下开关立即刷新,不等30ms
}
//处理按键输入
void keyEvent() {
    char ch;
    if (kbhit()) {//判断是否有按键输入
        
        ch=getch();//不需要按回车直接读取输入字符
        //scanf("%c", &c);// 会等待用户输入,阻塞程序运行
        if (ch == ' ') {//如果输入空格就跳跃
            jump();
        }
    }
}

void updateEnemy() {
    ////渲染小乌龟
    //if (torToiseExist) {
    //    putimagePNG2(torToiseX, torToiseY, WIN_WIDTH, &imgTortoise);
    //}
    
    //通用化地渲染障碍物
    for (int i = 0; i < OBSTACLE_COUNT; i++) {
        if (obstacles[i].exist) {//该种障碍物存在时进行渲染
            putimagePNG2(obstacles[i].x, obstacles[i].y, WIN_WIDTH, &obstacleImgs[obstacles[i].type][obstacles[i].imgIndex]);//渲染即将对应图片放到新位置(新x,y坐标),所取图片在数组中位置为:行为障碍物类型,列为障碍物序号
        }
    }
}

int main(void) {
    init();

    int timer = 0;//让画面不要太快刷新

    //让图片动起来
    while (1) {
        keyEvent();//按键使玩家跳跃
        timer+=getDelay();//getDelay()距离上次调用用了多少时间
        if (timer > 30) {//大于30ms再刷新
            timer = 0;
            update = true;
        }
        if(update){
            update = false;
            //跑一点打印一点就会抖动,准备好再打印就不会抖动。用双缓冲机制防止抖动
            BeginBatchDraw();
            updateBg();
            putimagePNG2(heroX, heroY, &imgHeros[heroIndex]);//打印玩家图片
            updateEnemy();
            EndBatchDraw();

            fly();//每次打印后改一下图片位置

        }



        //Sleep(30);//帧等待。帧打印过快看不出变化,每打印一次需进行帧等待
          //程序一般不加sleep,sleep意味着程序休眠,如果30ms内用户按下按键,程序不会响应
    }

    updateBg();

    system("pause");

    return 0;
}



运行结果及详细报错内容

如图

img

img

我的解答思路和尝试过的方法

尝试了安装vc2022库文件和添加vcruntime140d.dll到目录,无法解决

想要达到的结果

解决此bug

好家伙,你直接将一个vector加到另一个vector之中,内存不爆才怪呢