0

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;.
Lover of Structure
  • 1,561
  • 3
  • 11
  • 27
  • 4
    What is `a++ = b` supposed to do different than `a = b`? – Allan Wind Mar 17 '23 at 07:12
  • 1
    Because most CPUs can do `c[i++] = d` in one opcode. Too big an optimization to miss. :-) – oakad Mar 17 '23 at 07:12
  • 5
    If `c[x]` wasn't an l-value, how would you ever assign to an array element? – Mat Mar 17 '23 at 07:13
  • Is not a++ an rvalue that cannot be assigned to? – ulix Mar 17 '23 at 07:24
  • @AllanWind The result of `a++ = b` (upon a strict reading of the standard, modulo whatever it is that I presumably missed) would be identical to that of `a = b + 1`. – Lover of Structure Mar 17 '23 at 07:26
  • 7
    You apparently missed (or misinterpreted) 6.5.2.4/2 which states that *"The result of the postfix ++ operator is the **value** of the operand."* Emphasis added. – user3386109 Mar 17 '23 at 07:32
  • @user3386109 This looks like a good and relevant point to me. – Lover of Structure Mar 17 '23 at 07:47
  • 1
    `a++ = b` has 2 changes being applied to `a`. The context is not clear, which should happen first. Recall `=` is low on the [C Operator Precedence](https://en.cppreference.com/w/c/language/operator_precedence) list. So spec-wise `a++` happens first. Yet OP wants it the other way. Typical end result: `a = b`. – chux - Reinstate Monica Mar 17 '23 at 08:53
  • An assignment expression isn't an lvalue, because only one of the assignments would actually have a relevant effect. You can't do `(a = b) = c;`, because which would "win", `b` or `c`? Similarly, you can't do `(a += 1) = c;`. And similarly, you can't do `(a++) = c;`, i.e. `a++ = c;` But `a[i++] = b;` makes perfect sense: The `++` operator assigns to `i`, and the `=` operator assigns to `a[i]`. There is no conflict. The assignments affect two independent entities. – Tom Karzes Mar 17 '23 at 11:10
  • 1
    @chux-ReinstateMonica See my edit – but good point about operator precedence! – Lover of Structure Mar 17 '23 at 13:36
  • @chux-ReinstateMonica Another thought is that perhaps `++` and `--` have such high precedence just to resolve parsing ambiguities; that is, their place in the precedence hierarchy is syntactically motivated. (Their semantics needs to be described separately anyways.) – Lover of Structure Mar 17 '23 at 14:07
  • @chux-ReinstateMonica *Actually* (from Steve Summit)[https://www.eskimo.com/~scs/cclass/krnotes/sx5l.html]: "Note that precedence is *not* the same thing as order of evaluation. Precedence determines how an expression is parsed, and it has an influence on the order in which parts of it are evaluated, but the influence isn't as strong as you'd think." – Lover of Structure Mar 18 '23 at 09:59
  • @LoverofStructure Do you think `=` will evaluate before `++`? – chux - Reinstate Monica Mar 18 '23 at 15:06
  • @chux-ReinstateMonica Conceivably yes (if it weren't outlawed), and that's the point of the whole post. Operator precedence is about parse tree construction but doesn't determine evaluation order. Also the point of `++` is (semantically speaking) not evaluation but the side effect; compare how `++a` is an lvalue in C++. – Lover of Structure Mar 18 '23 at 15:49
  • @chux-ReinstateMonica Or, even if technically speaking "evaluation" of `a++` to the-value-`a` happens (or has to happen) before that of `=`, I don't see why its side effect (incrementing `a`) would necessarily have to happen before the `=`-assignment. – Lover of Structure Mar 18 '23 at 15:57
  • 1
    May be useful: [Does `i = x[i]++;` lead to undefined behavior?](https://stackoverflow.com/q/71843405/1778275). – pmor Mar 28 '23 at 11:32

5 Answers5

12

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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • **Comments have been [moved to chat](https://chat.stackoverflow.com/rooms/252915/discussion-on-answer-by-ikegami-why-is-ab-disallowed-while-cid-is-permi); please do not continue the discussion here.** Before posting a comment below this one, please review the [purposes of comments](/help/privileges/comment). Comments that do not request clarification or suggest improvements usually belong as an [answer](/help/how-to-answer), on [meta], or in [chat]. Comments continuing discussion may be removed. – Samuel Liew Apr 01 '23 at 13:26
4

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.

virolino
  • 2,073
  • 5
  • 21
  • My rationale is that `a++ = b;` would expand to `a = b; a++;`. – Lover of Structure Mar 17 '23 at 07:46
  • And that is another reason why that kind of "instruction" should be forbidden. Clarity is supposed to be more important than occasional "fun" or expectation. If you want fun, there are obfuscation contest already organized, you are welcome to join them. – virolino Mar 17 '23 at 08:50
  • 1
    @LoverofStructure Why conclude: `expand to a = b; a++;`? See [comment](https://stackoverflow.com/questions/75764642/why-is-a-b-disallowed-while-ci-d-is-permitted#comment133653513_75764642). – chux - Reinstate Monica Mar 17 '23 at 08:55
3

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.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
3

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.

haccks
  • 104,019
  • 25
  • 176
  • 264
1

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.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103