C语言控制台国际象棋程序两while循环行为不一致Bug排查
控制台C语言国际象棋游戏目标坐标校验Bug排查
项目背景
正在开发可直接在控制台运行的国际象棋游戏,当前已完成基础棋盘定义、坐标转换、输入校验的基础框架,核心实现代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #include <ctype.h> typedef struct { char type; bool alive; int coorx; int coory; } Piece; void print_board(char board[8][8]) { int i, j; for (i = 0; i < 8; i++) { for (j = 0; j < 8; j++) { printf("%c", board[i][j]); } printf("\n"); } } void convert_coordinates(char x, int y, int *cx, int *cy) { /* converts the square to the coordinate, for example a3 -> 05, g7 -> 61 */ switch (x) { case 'a': *cx = 0; break; case 'b': *cx = 1; break; case 'c': *cx = 2; break; case 'd': *cx = 3; break; case 'e': *cx = 4; break; case 'f': *cx = 5; break; case 'g': *cx = 6; break; case 'h': *cx = 7; break; } *cy = -(y - 8); } int main() { int y1, y2, csx, csy, dsx, dsy; /*inputted numbers from user on current and destination squares and coordinates of both*/ char x1, x2, cp; /*inputted chars from user and current piece*/ bool turn = 1, wcheck = 0, bcheck = 0; char square; char board[8][8] = { {'r','k','b','k','w','b','k','r'}, {'p','p','p','p','p','p','p','p'}, {' ',' ',' ',' ',' ',' ',' ',' '}, {' ',' ',' ',' ',' ',' ',' ',' '}, {' ',' ',' ',' ',' ',' ',' ',' '}, {' ',' ',' ',' ',' ',' ',' ',' '}, {'P','P','P','P','P','P','P','P'}, {'R','K','B','K','W','B','K','R'} }; Piece pieces[32] = { {'r', 0, 0, 0},{'k', 0, 0, 1},{'b', 0, 0, 2},{'k', 0, 0, 3},{'q', 0, 0, 4},{'b', 0, 0, 5},{'k',0, 0, 6},{'r', 0, 0, 7}, {'p', 0, 1, 0},{'p', 0, 1, 1},{'p', 0, 1, 2},{'p', 0, 1, 3},{'p', 0, 1, 4},{'p', 0, 1, 5},{'p',0, 1, 6},{'p', 0, 1, 7}, {'p', 0, 6, 0},{'p', 0, 6, 1},{'p', 0, 6, 2},{'p', 0, 6, 3},{'p', 0, 6, 4},{'p', 0, 6, 5},{'p',0, 6, 6},{'p', 0, 6, 7}, {'r', 0, 7, 0},{'k', 0, 7, 1},{'b', 0, 7, 2},{'k', 0, 7, 3},{'q', 0, 7, 4},{'b', 0, 7, 5},{'k',0, 7, 6},{'r', 0, 6, 6} }; printf("Type the coordinates of the piece you want to move.\n"); scanf(" %1c", &x1); scanf(" %1d", &y1); convert_coordinates(x1, y1, &csx, &csy); while ((y1 < 1 || y1 > 8) || !(x1 == 'a' || x1 == 'b' || x1 == 'c' || x1 == 'd' || x1 == 'e' || x1 == 'f' || x1 == 'g' || x1 == 'h') || !(isupper(board[csy][csx]) == turn)) { printf("Error, try again.\n"); scanf(" %1c", &x1); scanf(" %1d", &y1); convert_coordinates(x1, y1, &csx, &csy); }; /* Check if the square is correct */ printf("Type the coordinates of the square you want to move that piece to.\n"); scanf(" %1c", &x2); scanf(" %1d", &y2); convert_coordinates(x2, y2, &dsx, &dsy); while ((y2 < 1 || y2 > 8) || !(x2 == 'a' || x2 == 'b' || x2 == 'c' || x1 == 'd' || x2 == 'e' || x2 == 'f' || x2 == 'g' || x2 == 'h') || (isupper(board[dsy][dsx]) == turn)) { printf("Error, try again.\n"); scanf(" %1c", &x2); scanf(" %1d", &y2); convert_coordinates(x2, y2, &dsx, &dsy); }; /* Check if the square is correct */ printf("%c%d\n", x2, y2); if (isupper(board[dsy][dsx]) == turn) { printf("%d %d %c", dsx,dsy,board[dsy][dsx]); } turn = -turn; print_board(board); return 0; }
现有逻辑说明
- 棋子表示规则:白方棋子用大写字符存储,黑方棋子用小写字符存储,空棋盘格用
' '表示 - 回合标识:白方先手,
turn = 1代表白方回合,turn = 0代表黑方回合 - 起始坐标校验循环(第一个while):运行符合预期,校验规则为:
- 输入的行号必须在1-8范围内
- 输入的列标识必须是a-h之间的小写字母
- 选中的格子必须存在当前回合方的棋子(白方回合必须选中大写棋子,黑方回合必须选中小写棋子),空格、对方棋子都会被判定为非法
- 目标坐标校验循环(第二个while):预期校验规则为:
- 输入的行号必须在1-8范围内
- 输入的列标识必须是a-h之间的小写字母
- 禁止移动到己方棋子所在格子,空格、对方棋子均为合法目标
异常复现
白方先手回合,输入
e4作为起始坐标时,第一个校验循环会正确抛出Error, try again提示要求重输(初始棋盘e4为空格,不存在白方棋子);但通过起始坐标校验后(比如选中e2位置的白兵),输入e4作为目标坐标时,第二个校验循环不会触发错误提示,直接判定为合法输入通过校验。
根因定位
问题由1个确定性笔误bug、1个标准库使用逻辑漏洞,加1个潜在越界风险共同导致:
- 目标坐标列校验笔误(核心显性bug)
第二个while循环的列合法判断条件中,判断d列是否合法时误用了起始坐标变量x1,对应错误代码片段为:
这里的!(x2 == 'a' || x2 == 'b' || x2 == 'c' || x1 == 'd' || x2 == 'e' || x2 == 'f' || x2 == 'g' || x2 == 'h')x1 == 'd'应改为x2 == 'd'。该bug会导致d列的合法性判断完全绑定起始坐标的列值,和用户输入的目标列无关,直接破坏坐标校验逻辑的一致性。 isupper()返回值判断逻辑漏洞(隐性bug)
C标准规定isupper()等ctype.h系列函数的返回值规则为:符合判断条件时返回任意非零整数值,不符合时返回0,不保证返回值固定为1。直接将其返回值和值为1的bool变量turn做相等判断,在部分标准库实现下(比如返回2表示是大写字母),会出现“选中己方大写棋子却被判定为非己方棋子”的随机异常。- 坐标转换函数潜在越界风险
convert_coordinates函数的switch分支没有对a-h以外的非法输入做兜底处理,当用户输入非法列标识时,*cx会保持未初始化的野值,后续访问棋盘数组时会触发越界内存访问,导致不可预期的行为。
注:e4空格作为目标坐标直接通过校验本身是符合设定的“允许移动到空格”规则的,不属于bug——国际象棋中绝大多数走法都是移动到空格位置,只有吃子操作才会移动到有对方棋子的格子。
修复方案
- 修正第二个while循环列判断的笔误,将
x1 == 'd'改为x2 == 'd' - 所有
isupper()调用先强制转为bool值再做相等判断,比如将isupper(board[dsy][dsx]) == turn改为(bool)isupper(board[dsy][dsx]) == turn - 在
convert_coordinates的switch分支中增加default分支,对非法输入做统一错误处理,避免野值导致的数组越界 - 补充判断:禁止目标坐标和起始坐标完全相同(不允许原地不走棋)
内容的提问来源于stack exchange,提问作者chesser
相关产品推荐
相关产品推荐

