c_tetris

第 9 课:调试技巧 🐛

9.1 本课目标

学会 C 语言程序的调试方法,掌握常见问题排查技巧。


9.2 编译警告

开启所有警告

# Makefile
CFLAGS = -Wall -Wextra -std=c99

警告选项: | 选项 | 含义 | 作用 | |——|——|——| | -Wall | Warning All | 显示所有警告 | | -Wextra | Warning Extra | 显示额外警告 | | -Werror | Warning Error | 把警告当错误 |

常见警告及解决

警告 1:未使用变量

warning: unused variable 'x'

// 解决:删除或使用该变量
int x = 5;  // ❌
printf("%d", x);  // ✅

警告 2:隐式函数声明

warning: implicit declaration of function 'foo'

// 解决:添加函数声明或包含头文件
void foo(void);  // 声明
#include "foo.h" // 或包含头文件

警告 3:类型不匹配

warning: comparison between signed and unsigned

// 解决:统一类型
int x = -1;
unsigned int y = 5;
if (x < y) ...  // ❌ 警告

int x = -1;
int y = 5;
if (x < y) ...  // ✅

9.3 printf 调试法

基础用法

printf("DEBUG: x = %d\n", x);
printf("DEBUG: 位置 (%d, %d)\n", game->current.x, game->current.y);

调试输出技巧

1. 标记代码执行位置

printf("=== 进入 game_update() ===\n");
// ... 代码
printf("=== 离开 game_update() ===\n");

2. 打印关键变量

printf("分数:%d, 等级:%d, 行数:%d\n", 
       game->score, game->level, game->lines_cleared);

3. 条件打印

if (game->game_over) {
    printf("游戏结束!最终分数:%d\n", game->score);
}

4. 调试碰撞检测

bool check_collision(Tetromino* t, Board* board) {
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (t->shape[i][j]) {
                int x = t->x + j;
                int y = t->y + i;
                printf("检查 (%d,%d) 形状 [%d][%d]=%d\n", 
                       x, y, i, j, t->shape[i][j]);
            }
        }
    }
}

9.4 常见问题排查

问题 1:方块显示乱码

症状: 方块显示为 `` 或其他乱码

原因: 使用了中文字符或 Unicode

解决:

// ❌ 错误
mvaddstr(y, x, "██");

// ✅ 正确
mvaddch(y, x * 2, ACS_CKBOARD);
mvaddch(y, x * 2 + 1, ACS_CKBOARD);

问题 2:方块下落太快

症状: 方块瞬间到底,无法控制

原因: drop_interval 设置太小

解决:

// game_init() 中
game->drop_interval = 3000;  // 3 秒/格
// 不是 100 或 500

问题 3:碰撞检测失效

症状: 方块穿墙或穿过其他方块

排查步骤:

// 1. 添加调试输出
printf("方块位置:(%d, %d)\n", game->current.x, game->current.y);
printf("棋盘 [%d][%d] = %d\n", y, x, board->cells[y][x]);

// 2. 检查坐标计算
int x = t->x + j;  // 确认 t->x 正确
int y = t->y + i;  // 确认 t->y 正确

// 3. 检查边界条件
if (x < 0) printf("碰撞:左边界\n");
if (x >= BOARD_WIDTH) printf("碰撞:右边界\n");

问题 4:内存泄漏

检测工具:valgrind

# 安装
sudo apt install valgrind

# 运行
valgrind --leak-check=full ./tetris

# 输出示例
==1234== 40 bytes in 1 blocks are definitely lost
==1234==    at 0x4C2FB55: malloc (in ...)
==1234==    by 0x400ABC: game_create (game.c:10)

解决: 确保每个 malloc 都有对应的 free

Game* game = malloc(sizeof(Game));
// ... 使用
free(game);  // 别忘了!

9.5 调试版本

Makefile 添加 debug 目标

# 调试版本
debug: CFLAGS += -g -DDEBUG
debug: clean all

# 发布版本
release: CFLAGS += -O2 -DNDEBUG
release: clean all

条件编译调试代码

#ifdef DEBUG
    printf("DEBUG: 详细输出\n");
#endif

// 使用
make debug   // 编译调试版本,有调试输出
make release // 编译发布版本,无调试输出

9.6 分段调试法

隔离问题

// 问题:游戏运行不正常
// 方法:分段测试

// 1. 测试初始化
game_init(game);
printf("初始化完成\n");
getch();  // 暂停,检查状态

// 2. 测试渲染
game_render(game);
printf("渲染完成\n");
getch();

// 3. 测试输入
int key = input_get_key();
printf("按键:%d\n", key);

最小化测试

// 创建最小测试程序
#include "tetromino.h"

int main() {
    Tetromino t;
    tetromino_init(&t, TETRO_I);
    printf("方块初始化成功\n");
    printf("位置:(%d, %d)\n", t.x, t.y);
    return 0;
}

// 编译测试
gcc -Isrc src/tetromino.c test.c -o test
./test

✅ 本课检查清单


📝 小作业

  1. 在碰撞检测函数中添加调试输出

  2. 使用 valgrind 检查是否有内存泄漏

  3. 创建一个最小测试程序,只测试方块旋转功能


下一课:影子方块