文章摘要
文章讲述了作者对经典游戏《DOOM》的喜爱,提到这款31年前的游戏至今仍可在各种现代平台上运行。作者因缘际会成为Fedora Linux中多个DOOM相关软件包的维护者,并简要提及Fedora在每次新版本发布前会进行大规模软件包重建的流程。
文章总结
《毁灭战士》中的布尔值陷阱:一个未定义行为的调试故事
作为经典游戏《毁灭战士》的忠实粉丝,作者在维护Fedora Linux中多个相关软件包时遇到了一个有趣的调试案例。在Fedora 42大规模重建过程中,chocolate-doom包突然无法通过编译。
问题根源在于GCC 15.0.1将默认C标准从gnu17升级到gnu23。游戏源代码中自定义的布尔类型定义:
c
typedef enum {
false,
true
} boolean;
与新标准中的关键字产生冲突。作者最初通过修改条件编译指令解决了编译问题,但随后发现更严重的问题:当使用C23标准的_Bool类型时,游戏会在启动时崩溃。
通过深入调试发现,游戏代码中存在一个未初始化内存问题:全局数组sprtemp被memset为-1(0xffffffff),而当布尔类型为_Bool时,255(0xff)这个非法值会导致同时满足==true和==false的诡异现象。
最终查明这是典型的未定义行为(UB)案例,违反了C99标准6.2.6.1.5节关于对象表示的规定。这个案例生动展示了: 1. 编译器升级可能暴露隐藏问题 2. 类型系统的细微差别可能导致重大行为差异 3. 未初始化内存访问的危害性
项目维护者最终决定将代码明确标记为C99标准,并保留原有的整型布尔实现,以避免触及这些深层次的问题。这个调试过程充分展现了C语言编程中可能遇到的微妙陷阱。
评论总结
这篇评论围绕C语言中布尔类型的未定义行为展开讨论,主要观点如下:
调试方法争议
- 有评论认为直接阅读汇编代码效率低,应优先使用调试工具: "reading the generated assembler...is usually a slow way of debugging. I would honestly do [-fsanitize=undefined] first"(kccqzy)
- 也有用户分享类似调试经历: "I've been there: 'ah hah! I found you. hrm, now what does this mean...'"(djoldman)
语言标准选择
- 支持明确指定旧版C标准: "you can't expect the old C code to follow the rules of the new versions"(Joker_vD)
- 批评C99布尔类型的严格规定: "C...decided to favor pedantry over working code"(lowbloodsugar)
类型系统问题
- 指出memset滥用是根本原因: "undermines the type system by spraying -1's into an array of structs"(munchler)
- 解释_Bool类型转换规则被绕过: "The memset() bypasses this rule, boom"(i-con)
布尔类型使用习惯
- 支持传统零/非零判断: "zero and nonzero are enough and very natural to use"(userbinator)
- 反对冗余布尔比较: "Seeing '== false'...triggers the suspicion...'(x == false)== true'"(userbinator)
个人经历分享
- 回忆类似编程错误: "I got the same invalid bool which was both true and false"(theamk)
- 调侃汇编调试过程: "Ah yes, obfuscation"(allreduce)