这个问题通常会引发一连串的答案,试图通过运算符优先级的概念来解释这种情况。实际上,它不能这样解释,因为这是输入的典型示例,“运算符优先级”之类的替代概念在此基础上分解。您可能知道,C 中确实没有“运算符优先级”。只有语法分组,通常无法通过运算符的任何线性排序来精确表达。
让我们来看看语言规范是怎么说的。在这种情况下,C 语法的相关部分是?: 运算符和= 运算符的语法。对于?: 运算符,它是
conditional-expression:
logical-OR-expression
logical-OR-expression ? expression : conditional-expression
对于= 运算符,它是
assignment-expression:
conditional-expression
unary-expression assignment-operator assignment-expression
在第一种情况下,关键部分是?: 运算符的最后一个操作数:它不是expression,而是conditional-expression。 conditional-expression 是 C 表达式语法的不同入口点:它在不再可能将顶级 = 运算符包含到 conditional-expression 的位置“进入”语法。将= 运算符“走私”到conditional-expression 中的唯一方法是将语法一直下降到最底层
primary-expression:
identifier
constant
string-literal
( expression )
generic-selection
然后使用( expression ) 分支一直环绕到顶部。这意味着 conditional-expression 只有在显式包装在 (...) 中时才能包含 = 运算符。例如。语法禁止您将 g = b 作为 ?: 运算符的最后一个操作数。如果你想要这样的东西,你必须明确地用括号括起来:<smth> ? <smth> : (g = b)。
第二条语法也存在非常相似的情况:赋值运算符。赋值的左侧 (LHS) 是 unary-expression。而unary-expression 在包含顶级?: 运算符为时已晚的地方“进入”了C 表达式的一般语法。从unary-expression 到达?: 运算符的唯一方法是一直下降到primary-expression 并采用( expression ) 分支。这意味着语法禁止您将 a > b ? g = a : g 作为 = 运算符的 LHS 操作数。如果你想要这样的东西,你必须明确地将它作为父级:(a > b ? g = a : g) = <smth>。
出于这个原因,声称“运算符优先级”的“流行”答案使语言将您的表达式解析为
(a > b ? g = a : g) = b
实际上是完全不正确的。实际上,正式的 C 语法中没有派生树可以使您的输入符合 C 语言的语法。您的输入根本无法解析。它不是一种表达方式。它只是在语法上无效。 C 语言将其视为句法乱码。
现在,在实践中,您可能会看到一些实现响应“需要左值作为赋值的左操作数”诊断消息。形式上,这是一个误导性的诊断信息。由于上述输入不满足C语言表达式的语法,因此其中没有“赋值”,没有“左操作数”,也没有有意义的“左值”要求。
为什么编译器会发出这个奇怪的信息?他们很可能确实将此输入解析为有效的 C 表达式
(a > b ? g = a : g) = b
?: 的结果在 C 中绝不是左值,因此会出现错误。但是,对您的输入的这种解释是非标准(扩展?)行为,在正式的 C 语言中没有基础。特定实现的这种行为可能是由于它们试图协调 C 和 C++ 语法(这在这方面完全不同)、它们试图产生更易读(尽管是“假”)错误消息或其他原因造成的。
通常,在这样的实现中,类似的问题也会在输入的情况下弹出
a + b = 5
会发出同样的错误,提示解析 (a + b) = 5,而从迂腐的角度来看,a + b = 5 根本无法解析为表达式(原因与上述相同)。
再次,正式地说,编译器“损坏”是不够的:编译器需要检测约束违规并发出一些诊断消息,这正是这里发生的情况。诊断消息的文本没有正确反映问题的性质这一事实是无关紧要的(编译器可以简单地说“哈哈哈!”)。但是,这种误导性诊断的一个不良后果是它会误导用户误解问题,顺便说一句,从发布到此问题的大量正式错误答案中可以明显看出这一点。