1

I have just written a sample code to try it out. Surprisingly, I did not get any compilation failure. As per C,we should have declaration followed by initialization or use. Kindly explain.

#include <stdio.h>

int main(void) {

    int a = a = 1; //Why it compiles??
    printf("%d",a);
    return 0;
}

Above code is compiled successfully and outputs 1. Please explain and also provide any input from standard which allows this.

alk
  • 69,737
  • 10
  • 105
  • 255
akjlab
  • 170
  • 6

3 Answers3

3

The definition

int a = a = 1;

is equal to

int a = (a = 1);

and is also roughly equivalent to

int a;
a = (a = 1);

When you use a in the initialization, it has already been defined, it exists and can be assigned to. And more importantly, since it's defined then it can be used as a source for its own initialization.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • There is no equivalence between `int a = a = 1;` and `int a; a = (a = 1);`. The latter invokes [undefined behavior](https://stackoverflow.com/questions/949433/why-are-these-constructs-using-undefined-behavior-in-c). The former happens not to because there is a sequence point at the [end of a full initialization expression](https://learn.microsoft.com/en-us/cpp/c-language/c-sequence-points) The output of [Tsnippet](https://taas.trust-in-soft.com/tsnippet/t/047f675a) here also indicates that the latter is UB. – Pascal Cuoq Apr 19 '18 at 06:45
  • @PascalCuoq That's why I said "***roughly*** equivalent". And yes it's UB because the side-effect of the assignments (the actual setting of `a` to `1`) is not sequenced, there are two `a = 1` assignments that can happen in any order. The result will still be that `a` is assigned the value `1`. – Some programmer dude Apr 19 '18 at 07:10
  • 2
    Undefined behavior does not have a result. – Pascal Cuoq Apr 19 '18 at 07:12
  • @PascalCuoq There is a sequence point after a full declarator, in this case after`int a = a = 1`. There is also a sequence point after a full expression, that is at the `;`. Therefore both versions invoke UB, not just the latter one. – Lundin Apr 19 '18 at 07:46
  • @Lundin An initializer is a full expression. In `int a = a = 1;` the initializer is `a = 1`. There is a sequence point after that. https://port70.net/~nsz/c/c11/n1570.html#Cp1 – Pascal Cuoq Apr 19 '18 at 07:56
  • @PascalCuoq Annex C is not normative. In the normative text 6.7 we can only read "If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator, or by the end of its init-declarator if it has an initializer;". Where one valid syntax for `init-declarator:` is specified as `declarator = initializer`. The definition of a declarator is found in 6.7.6 where we can read "A full declarator is a declarator that is not part of another declarator. The end of a full declarator is a sequence point.". – Lundin Apr 19 '18 at 08:26
  • @Lundin You are reading from the wrong part of the standard. The sequence point at the end of the declarator is in addition to the sequence point of the initializer. I'm reading from https://port70.net/~nsz/c/c11/n1570.html#6.8p4 which says that an initializer is a full expression, and then that there is a sequence point at the end of a full expression. – Pascal Cuoq Apr 19 '18 at 10:08
  • 1
    @Lundin if you reject Annex C as non-normative, I already know what you will think of this argument, but both Clang and GCC, quality compilers which warn about `x = (x = 1);`, omit the warning about `int x = x = 1;`. It is possible to argue that the standard is ambiguous, but there appears to be several of us that interpret it as making `int x = x = 1;` defined. – Pascal Cuoq Apr 19 '18 at 10:14
  • @PascalCuoq So you are saying that in the expression `int a = a = 1`, `a` is _not_ part of the declarator? How does that make sense? It can't both be a declarator and not a declarator at the same time. – Lundin Apr 19 '18 at 10:57
  • 1
    @Lundin With `int a = a = 1;` (which isn't an expression to nitpick) you have `int a` where `a` is the declarator. Then you have `a = 1` which is the initializer. – Some programmer dude Apr 19 '18 at 11:01
  • @Someprogrammerdude If `a` in `a = 1` is not a declarator, then what is `a`? It would then be an unknown identifier not yet declared. – Lundin Apr 19 '18 at 11:05
  • @Lundin That's the thing, `int a` is defining the variable `a`. From that point onward it can be referenced, even in the initializer of itself. It's syntactically valid do say `int a = a;`. It's useless and semantically dubious, but it's allowed since the variable have been defined when the initializer is evaluated. And't that's why `int a = a = 1` is working as well, because it's separated into the definition of `a` and then separately the initialization of `a` (which happens to be an assignment to `a`). – Some programmer dude Apr 19 '18 at 11:10
  • @Someprogrammerdude No, `int a` is not "defining" or declaring a variable. The syntax for declaring a variable is `declarator = initializer`, see 6.7. It doesn't really mention what happens when the declarator is part of the initializer, possibly it is a syntax error. – Lundin Apr 19 '18 at 11:16
  • @Lundin Of course the second `a` in `int a = a = 1;` is **not** a declarator. A declarator is something that occurs in a specific context as part of, say, [a declaration](https://port70.net/~nsz/c/c11/n1570.html#6.7p1). If we are arguing about something as unambiguous as the grammar now, the discussion has erred into the ridiculous and it's time for me to get out of it. – Pascal Cuoq Apr 19 '18 at 12:08
3

Each assignment expression like a = 1 has - besides the "side effect" of assigning the value 1 to a - a result value, which is the value of a after the assignment (cf, for example, cppreference/assignment):

Assignment also returns the same value as what was stored in lhs (so that expressions such as a = b = c are possible).

Hence, if you write, for example, int a; printf("%d",(a=1)), the output will be 1.

If you know chain assignments like in int a; a = a = 1, then this is equivalent to int a; a = (a=1), and - as the result of (a=1) is 1, the result of a = (a=1) is 1, too.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • And since `a = 1` is a side effect unsequenced to the left operand `a` of the first `=`, the initialization invokes undefined behavior. – Lundin Apr 19 '18 at 07:55
  • 2
    There is only one assignment operator in `int a = a = 1;` , the first `=` symbol is part of initialization syntax. It's not the same as `int a; a = a = 1;` – M.M Apr 19 '18 at 08:48
  • @M.M One assignment but two `assignment-expression`s. – Lundin Apr 19 '18 at 11:00
  • @Lundin technically three, as `a` and `1` both match the grammatical category *assignment-expression* (since primary-expression is a subset of it). But that has not much to do with the question – M.M Apr 19 '18 at 11:06
1

The C standard does not define the behavior in this case, not because of the rule about unsequenced effects or explicit statement but rather because it fails to address the situation.

C 2011 (unofficial draft N1570) clause 6.7, paragraph 1, shows us the overall grammar of declarations. In this grammar, int a = a = 1; is a declaration in which:

  • int is a declaration-specifiers which consists solely of the type-specifier int.
  • a = a = 1 is an init-declarator, in which a is a declarator and a = 1 is an initializer. The declarator consists solely of the identifier a.

6.7.6 3 defines a full declarator to be a declarator that is not part of another declarator, and it says the end of a full declarator is a sequence point. However, these are not necessary for our analysis.

6.7.9 8 says “An initializer specifies the initial value stored in an object.”

6.7.9 11 says “The initializer for a scalar shall be a single expression, optionally enclosed in braces. The initial value of the object is that of the expression (after conversion); the same type constraints and conversions as for simple assignment apply, taking the type of the scalar to be the unqualified version of its declared type.”

So, on one hand, the initializer, which has the value 1, specifies the initial value stored in a. On the other hand, the expression a = 1 has the side effect of storing 1 in a. I do not see anything in the C standard that says which occurs first. The rules about sequencing within expressions apply only to the evaluation of the initializer; they do not tell us the order of giving the “initial value” to a and the side effect of assigning to it.

It is reasonable to conclude that, whether a is given the initial value 1 or is assigned the value 1, it ends up with the value 1, so the behavior is defined. However, the standard famously makes it undefined behavior to modify the value of an object twice in an unsequenced way, even if the value being written is the same. The explicit statement of that rule is in 6.5 2, which applies to expressions, and hence does not apply in this situation where we have a conflict between an initialization and an expression. However, we might interpret the spirit of the standard to be:

  • In order to afford an implementation opportunity to do whatever it needs to do to store (or modify) a new value in an object, a sequencing for the store relative to other stores (or modifications) must be defined.
  • The standard fails to define a sequence for the initialization and the assignment side effect, and therefore it fails to afford the implementation this needed constraint.

Thus, the standard fails to specify the behavior in a way that guarantees an implementation will produce defined behavior.

Additionally, we can consider int a = 2 + (a = 1). In this case, the value of the initializer is 3, but the side effect assigns 1 to a. For this declaration, the standard does not say which value prevails (except that one might interpret “initial value” literally, thus implying that 3 must be assigned first, so the side effect must be later).

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312