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),通常在布尔值上采用短路求值。