为什么不允许 a++=b,而允许 c[i++]=d?

Lov*_*ure 0 c lvalue language-lawyer pre-increment post-increment

为什么按照标准不允许a++ = b;,而c[i++] = d;允许呢?

(显然,a++ = b;这是一种不好的风格,但这是一个关于仔细阅读 C 语言标准的问题。)

这是强制性的最小示例:

#include <stdio.h>

int main(void)
{
        int a = 10;
        int b = 20;
        int i = 1;
        int c[] = {30, 40};
        int d = 50;

        a++ = b; // error
        c[i++] = d;

        printf("%d\n", a); // [21]
        printf("%d\n", b); // 20
        printf("%d\n", i); // 2
        printf("%d\n", c[0]); // 30
        printf("%d\n", c[1]); // 50
        printf("%d\n", d); // 50

        return 0;
}
Run Code Online (Sandbox Code Playgroud)

-std=c90当使用或进行编译时,GCC 会发出以下错误-std-c17:

error: lvalue required as left operand of assignment
Run Code Online (Sandbox Code Playgroud)

根据 K&R (2e)、A7.3.4 和 A7.4.1

++[后缀/前缀或]的结果--不是左值。

a++被认为不是左值,但是从什么措辞可以明确看出它c[i++] 是左值?至于C11标准,我找不到任何关于这两者的规定。

对于它的价值:如果不被禁止,我会解释a++ = b;为a = b; a = a+1;.


编辑:

有些人(有理由)问为什么人们会在语义上假设一个陈述是a++ = b;有道理的。

我经常尝试将棘手的语法结构转换为等效但更简单的东西。(我们都承认,前置和后置自增/自减运算符不仅很棘手;它们是语法灾难:它们可以深深嵌入到语句中,但具有必须在之前或之后执行的效果。)我通常会假设任何形式的非病态陈述

陈述(++w,--x,y++,z--)

相当于

w += 1;
x -= 1;
陈述(w,x,y,z)
y += 1;
z -= 1;

其中语句前和语句后分配以实现定义的方式排序。当然,问题是什么算作“非病态”(或者我们是否应该将其定义为“前增量和后增量之间的顺序无关紧要的情况”),但是,把这个暂时先把问题放在一边,程序员假设前置和后置自增/自减表达式在语法上等同于删除了这些运算符的相应形式,这并非没有道理。

说“此类运算符剥夺其左值质量的参数表达式”是完全有效的,并且确实回答了我的问题,但是:

  • 如果这个假设没有融入一个人的思维方式,其他解释(例如我上面写的)是可以想象的。(也就是说,从语言设计的角度来看(在我看来),前/后自增/自减表达式失去其左值质量并不是语法上的必要性。)
  • 对我来说,引用 K&R 的措辞(“后缀/前缀++或--] 的结果不是左值。”)似乎只是为了禁止像a++ = b;.

ike*_*ami 12

从什么措辞中可以明确看出这c[i++]是左值?

c[i]定义为*(c+i)

6.5.2.1.2 后缀表达式后跟方括号中的表达式[]是数组对象元素的下标指定。下标运算符的定义[]与E1[E2]相同(*((E1)+(E2)))。[...]

这给了我们一个左值。

6.5.3.2.4 一元*运算符表示间接。如果操作数指向函数,则结果是函数指示符;如果它指向一个对象,则结果是指定该对象的左值。[...]


你能修改 的结果吗5+1?当然不是。(5+1) = 3;完全没有意义。结果5+1不是可以保持价值的东西。您稍后无法获取分配给它的值。分配给 的结果5+1完全是胡说八道。

的结果a++是 的旧值a。它不可能是a它本身,因为它a不再具有正确的值。它只是一个短暂的值,就像 的结果一样5+1。它不是一个可以保值的东西。分配给它绝对没有意义。

用技术术语来说,a++它不是一个可修改的左值,因为它不是一个左值,因为它不潜在地引用一个对象。

6.3.2.1.1 左值是一个可能指定对象的表达式(具有除 void 之外的对象类型)。[...]


参考资料来自 C17 标准。


vir*_*ino 5

c[i++] = d;

这很好,因为它可以翻译为:

c[i] = d;
i++;
Run Code Online (Sandbox Code Playgroud)

a++=b;

它不能扩展到任何有意义或有用的东西。这两个操作应该像这样展开:

a++;
<result_of_a++> = b;
Run Code Online (Sandbox Code Playgroud)

这是无法实现的,因为 a++ 的结果是一个值,而不是一个“位置”。您不能将一个值分配给另一个值。