C语言记忆游戏程序在序列长度增至5位时出现运行时错误
Simple Simon记忆游戏数组值异常问题解决
问题描述
编写的Simple Simon记忆游戏初始3位序列运行正常,但当序列长度增至5位时,对比用户输入数组guessedNumArr与目标数组numToGuess时,numToGuess[0]的值会被莫名修改,导致玩家直接失败。游戏规则为:电脑短时间显示数字序列,玩家记忆后输入相同序列,连续3次成功则序列长度增加,目标是尽可能延续游戏。
核心错误分析
变长数组(VLA)的误用:
代码中numToGuess[difficulty]和guessedNumArr[difficulty]是变长数组,在main函数开头声明时difficulty的值为5,后续difficulty增加后,数组实际长度仍为5,当访问索引超过4的元素时,会发生栈内存越界,破坏相邻变量(包括numToGuess[0])的值。scanf格式符不匹配导致栈溢出:
处理用户输入Y/N时使用scanf("%s", &Y_N),但Y_N是单个char类型,%s会读取字符串并写入终止符\0,这会超出Y_N的内存空间,覆盖栈中其他变量(如numToGuess的元素)。随机数种子重复初始化:
srand(time(NULL))放在循环内部,每次循环都会重置随机数种子。由于time(NULL)返回秒级时间,短时间内多次循环会导致生成相同的随机序列,同时也可能影响程序执行时序,间接引发内存问题。
修复方案
- 改用动态内存分配:使用
malloc和realloc根据当前difficulty动态调整数组大小,避免越界。 - 修正
scanf格式符:对单个字符输入使用%c,并通过getchar()处理输入缓冲区的换行符,避免残留字符干扰后续输入。 - 初始化随机数种子一次:将
srand(time(NULL))移到main函数开头,仅初始化一次。 - 移除
goto语句:用循环结构替代goto,提升代码可读性和可维护性。
完整修正代码
#include <stdio.h> #include <stdbool.h> #include <time.h> #include <stdlib.h> #include <ctype.h> int main() { bool play = true; time_t now; int try_count = 1, anotherGame; int difficulty = 3; // 初始难度改为3,符合游戏描述 int *numToGuess = malloc(difficulty * sizeof(int)); int *guessedNumArr = malloc(difficulty * sizeof(int)); int count = 0, guessedNum, singleScore = 0, totalScore = 0; if (!numToGuess || !guessedNumArr) { printf("内存分配失败\n"); return 1; } // 仅初始化一次随机数种子 srand(time(NULL)); puts("\nSimple Simon is a memory-test game. In this game, the\n" "computer displays a sequence of digits on the screen for a short period of time.\n" "You then have to memorize them, and when the digits disappear from the screen,\n" "you must enter exactly the same sequence of digits. " "Each time you succeed 3 times in a row, the process repeats with a longer sequence of digits for you to try.\n" "The objective is to continue the process for as long as possible\n" "Press enter to play"); // 处理换行符 getchar(); do { int consecutive_success = 0; while (consecutive_success < 3) { const unsigned int DELAY = 2; // 生成目标序列 for (int i = 0; i < difficulty; i++) { numToGuess[i] = rand() % 10; printf("%d ", numToGuess[i]); } // 延迟后清空屏幕(用换行替代,实际可根据系统调用清屏命令) now = clock(); while (clock() - now < DELAY * CLOCKS_PER_SEC); puts("\n\n"); // 获取玩家输入 puts("Try to enter the same exact sequence of numbers, remember to put a space in between the numbers:\n"); for (int k = 0; k < difficulty; k++) { scanf("%d", &guessedNum); guessedNumArr[k] = guessedNum; } // 清除输入缓冲区的换行符 while (getchar() != '\n'); // 对比输入与目标序列 count = 0; for (int j = 0; j < difficulty; j++) { printf("%d --- %d\n", guessedNumArr[j], numToGuess[j]); if (guessedNumArr[j] == numToGuess[j]) { count++; } } try_count++; if (count == difficulty) { consecutive_success++; totalScore++; printf("正确!已连续成功%d次\n", consecutive_success); } else { char Y_N; printf("抱歉,你输了。总得分:%d。是否再玩一次?Y/N\n", totalScore); while (true) { scanf(" %c", &Y_N); // 空格跳过空白字符 Y_N = tolower(Y_N); if (Y_N == 'y') { try_count = 0; totalScore = 0; consecutive_success = 0; difficulty = 3; // 重新分配初始难度的内存 numToGuess = realloc(numToGuess, difficulty * sizeof(int)); guessedNumArr = realloc(guessedNumArr, difficulty * sizeof(int)); if (!numToGuess || !guessedNumArr) { printf("内存分配失败\n"); return 1; } break; } else if (Y_N == 'n') { printf("游戏结束\n"); play = false; break; } else { printf("请输入Y或N\n"); } } if (!play) break; } } if (!play) break; // 连续成功3次,询问是否继续 int gotItwrong = 0; char yOrN; while (true) { if (gotItwrong == 0) { puts("已连续成功3次,输入Y继续(序列长度+1),输入N结束游戏\n"); } scanf(" %c", &yOrN); yOrN = tolower(yOrN); if (yOrN == 'y') { difficulty++; // 重新分配内存 numToGuess = realloc(numToGuess, difficulty * sizeof(int)); guessedNumArr = realloc(guessedNumArr, difficulty * sizeof(int)); if (!numToGuess || !guessedNumArr) { printf("内存分配失败\n"); return 1; } consecutive_success = 0; break; } else if (yOrN == 'n') { printf("游戏结束,总得分:%d\n", totalScore); play = false; break; } else { printf("请输入Y或N\n"); gotItwrong++; } } } while (play == true); // 释放动态分配的内存 free(numToGuess); free(guessedNumArr); return 0; }
内容的提问来源于stack exchange,提问作者Giacomo D.G.
相关产品推荐
相关产品推荐

