Dennis Ritchie 关于 `&&` || 与 `==` 等优先级的看法 (1982)
C 及相关语言中的布尔和位运算符优先级——尤其是 `&&`、`||`、`&`、`|` 和 `==`——被审视为长期以来细微 bug 和混淆的来源。评论者分析了 Dennis Ritchie 对诸如让 `&` 的优先级低于 `==` 之类设计的历史原因,并将其与布尔代数、集合论和概率中的数学直觉进行对比。许多人主张使用显式括号或采用限制歧义表达式的语言设计,并指出新语言和 linter 越来越倾向于清晰性,而不是依赖记忆优先级规则。
主要困惑:逻辑运算符的优先级
- 许多人承认自己并不能稳定地记住
&&与||的优先级(以及 SQL 中常见的AND/OR),因此会选择在混合表达式中始终加括号。 - 也有人认为开发者应该直接记住规则,并把
&&/||的优先级类比为*/+。 - 还有不少人指出,即使知道规则,在切换到优先级不同的语言时也会踩坑(例如 shell)。
算术、布尔代数与助记法
- 多条评论将
&&与 0/1 值中的乘法联系起来,将||与加法联系起来:积 = AND,(饱和)和 = OR。 - 也有人更喜欢集合论:
&&视为交集,||视为并集,其分配律与布尔代数相呼应。 - 有些人指出,XOR 与模 2 算术中的加法更一致,但这种类比仍有助于记住相对优先级。
括号:风格与安全
- 很多人强烈支持对任何非平凡的布尔表达式显式加括号,即使已经知道优先级。
- 理由包括:
- 让未来的读者和代码审查者更容易理解。
- 避免表达式被修改或复制时出现细微 bug。
- 在真实世界 bug 之后,欢迎那些会对
a && b || c这类表达式报警的 linter。
- 反方观点:
- 过多括号会增加噪音和认知负担;括号应当表示覆盖默认规则,而不是重复默认规则。
- 不必要的
== true/== false以及过度加括号,被视为理解不足的迹象。
C 的位运算与比较优先级(“真正的” Ritchie 话题)
- 有人澄清,原文主要讨论的是为什么位运算
&/|的优先级会 低于==等运算,而不是&&与||。 - 从历史上看,在
&&出现之前,&曾被用作逻辑 AND,因此a==b & c==d需要按逻辑方式工作;后来这个选择导致像addr & mask == 0这样的表达式会以不直观的方式解析为addr & (mask == 0)。 - 评论者普遍认为这被广泛视为一个设计错误,但由于向后兼容性而被保留了下来。
语言设计替代方案
- 有些语言在混用不同中缀运算符时要求加括号(逻辑运算在 Ada 中如此;Pony、部分 Zig、WGSL、Carbon 风格的方法也是如此)。
- 另一些语言会避免或尽量减少优先级(Smalltalk 采用从左到右;Lisp/Scheme 通过前缀表示法)。
- 严格类型或较新的语言会调整优先级(Go/Rust/Swift、Python),或以受控方式合并位运算与逻辑运算符(例如 Sail),通常在布尔值上采用短路求值。