You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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个潜在越界风险共同导致:

  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列的合法性判断完全绑定起始坐标的列值,和用户输入的目标列无关,直接破坏坐标校验逻辑的一致性。
  2. isupper()返回值判断逻辑漏洞(隐性bug)
    C标准规定isupper()等ctype.h系列函数的返回值规则为:符合判断条件时返回任意非零整数值,不符合时返回0,不保证返回值固定为1。直接将其返回值和值为1的bool变量turn做相等判断,在部分标准库实现下(比如返回2表示是大写字母),会出现“选中己方大写棋子却被判定为非己方棋子”的随机异常。
  3. 坐标转换函数潜在越界风险
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:01:14