Search code examples
clanguage-lawyerlvaluepost-incrementpre-increment

Why is a++=b disallowed, while c[i++]=d is permitted?


Why according to the standard is a++ = b; disallowed, while c[i++] = d; is permitted?

(Obviously, a++ = b; would be bad style, but this is a question about a close reading of the C language standard.)

Here is the obligatory minimal example:

#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;
}

GCC emits the following error, when compiling this with -std=c90 or -std-c17:

error: lvalue required as left operand of assignment

According to K&R (2e), A7.3.4 and A7.4.1

The result [of postfix/prefix ++ or --] is not an lvalue.

a++ is considered to be not an lvalue, but from what wording does it explicitly follow that c[i++] is an lvalue? Turning to the C11 standard, I can't find any provisions about either.

For what it's worth: If it weren't disallowed, I would interpret a++ = b; as a = b; a = a+1;.


Edit:

Some people have (justifiedly) asked why one would semantically assume a statement like a++ = b; to make sense.

I often try to convert tricky syntactic constructs into something equivalent-but-simpler. (Let's all admit that pre- and post-increment/decrement operators aren't just tricky; they're a syntactic catastrophe: they can be deeply embedded in a statement but have the effect of something having to be executed before or after.) I was generally going with the assumption that any non-pathological statement of the form

statement(++w, --x, y++, z--)

is equivalent to

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

where the pre- and post-statement assignments are ordered in an implementation-defined manner. Of course the question is what counts as "non-pathological" (or whether we should even define it as "cases for which the order among the pre-increments and among the post-increments doesn't matter"), but, putting this concern aside for a moment, it is not unreasonable for a programmer to assume that pre- and post-incremented/decremented expressions are otherwise syntactically equivalent to their corresponding forms with these operators removed.

Saying that "such operators strip their argument expressions of their lvalue quality" is entirely valid and does answer my question, but:

  • If this assumption isn't built into one's mindset, other interpretations (such as what I wrote above) are conceivable. (That is, from a language design perspective (and in my opinion), pre-/post-increment/decrement expressions losing their lvalue quality is not a syntactic necessity.)
  • To me it seems a bit that the wording quoted from K&R ("The result [of postfix/prefix ++ or --] is not an lvalue.") was put in simply to disallow assignments like a++ = b;.

Solution

  • from what wording does it explicitly follow that c[i++] is an lvalue?

    c[i] is defined as *(c+i)

    6.5.2.1.2 A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). [...]

    And that give us an lvalue.

    6.5.3.2.4 The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. [...]


    Could you modify the result of 5+1? Of course not. (5+1) = 3; makes absolutely no sense. The result of 5+1 is not something that can hold a value. You couldn't fetch the value you assigned to it a later time. Assigning to the result of 5+1 is complete nonsense.

    The result of a++ is the old value of a. It can't be a itself, since a no longer has the correct value. It just an ephemeral value like the result of 5+1. It's not a something that can hold value. It makes absolutely no sense to assign to it.

    In technical terms, a++ isn't a modifiable lvalue because it's not an lvalue because it doesn't potentially refer to an object.

    6.3.2.1.1 An lvalue is an expression (with an object type other than void) that potentially designates an object. [...]


    References are from the C17 standard.