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

333 views Asked by At

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;.
5

There are 5 answers

1
ikegami On BEST ANSWER

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.

3
virolino On

c[i++] = d;

This is fine because it translates to:

c[i] = d;
i++;

a++ = b;

It cannot be expanded to anything meaningful or useful. The two operations should be expanded like this:

a++;
<result_of_a++> = b;

and this cannot be implemented, because the result of a++ is a value, not a "place". You cannot assign a value to another value.

0
Jean-Baptiste Yunès On

Not to be too formal, the rationale is the following.

Pay attention to the different statuses of a and b in the expression a=b. An expression like v as no meaning per-se, it depends where that expression appears. It sometimes denotes the value of some storage or the storage itself.

In the assignment a=b:

  • a is the receiver, means that it represents the storage where a value will be stored.
  • b is a value contained in a storage.

Think of a=666 and 3=b, the later has no meaning.

a is an l-value (left operand of assignment), the value that represents where the storage will take place.

b is an r-value (right operand of assignment) or simply a value, the value of b that will be stored into a.

a++ can't be an l-value, it is a pure value (as 666 is), i.e. the value of a before the incrementation of its content.

c[i++] is permitted because in that expression i++ is a value, that is used to index the array c, like in c[some value], which in turn can be used as l-value or r-value, it denotes either the storage or the value inside.

0
haccks On

a++ will result in a value. As standard says:

6.5.2.4/2

The result of the postfix ++ operator is the value of the operand.

Assignment operator = needs an l-value as it's left operand, but a++ is not an l-value. While in case of c[i++], any sub-expression inside the [] is the index of the array and should be evaluated as an integer value. This sub-expression may or may not be a variable or l-value or an integer literal. The sub-expression c[i++] it self is an l-value though.

That being said, a++ can't be the left operand of = operator. So, a++ = b; is syntactically an invalid statement.

0
Steve Summit On

Let's look at a slightly different case. Let's look at the hypothetical expression

++a = b;

Now, ++a is exactly equivalent to a += 1, which is (almost) exactly equivalent to a = a + 1. But suppose we make either of those replacements:

(a += 1) = b;

(a = a + 1) = b;

Would you expect either of those expressions to be equivalent to a = b followed by an increment of a? I don't think so. And although a++ is not equivalent in the same way (that is, it yields the old value of a rather than the new), it's equally wrong to put it on the left hand side of an assignment operator.

And that's the issue here: "Things that you can put on the left hand side of an assignment operator", or lvalues. A variable name like a is always an lvalue. A value computation like a + 1 or a++ is never an lvalue. But an array-subscripting expression like c[i] is always an lvalue, and it's an lvalue regardless of what goes on inside the index expression. c[i++] = d is fine, and c[i++ + f() + j++ + k++] = d would be fine, too, and it would only become undefined if you did something like c[i++] = e[i++] (or, God help you, c[i++ + i++] = d).


Addendum: I said:

Would you expect either of those expressions to be equivalent to a = b followed by an increment of a? I don't think so.

My wording there seems to suggest that it's intuitively obvious that (a += 1) = b (and by extension ++a = b) is meaningless. And it is intuitively obvious — to me, anyway. :-) But I'm biased, because I've been programming in C for way too long, and in actual fact, calling these meaningless is certainly not the only possible interpretation. In fact, in C++, ++a actually is an lvalue, and so evidently are the other two. So it's not a very strong argument to use ++a = b as an analogy for why a++ = b can't work.