11
int test[2] = { 45, test[0] };
int x = (x = 111);
cout << test[0] << " " << test[1] << " " << x << "\n"; // 45 45 111

Are the assignments in the first two lines legal? Visual Studio 2010 compiles and runs it without any errors or warnings but it seems like an odd case that could possibly be undefined, so I wanted to confirm that it is acceptable. Visual Studio does warn me if I do something blatantly reflexive (and presumably undefined) like int x = x; so I'm wondering how these situations it seems to allow are handled.

Ram
  • 3,092
  • 10
  • 40
  • 56
0x5f3759df
  • 2,349
  • 1
  • 20
  • 25
  • It's worth nothing that `int x = (x + 1)` also compiles and sets `x` to `1` (with GCC on Linux, anyway). – apsillers Nov 09 '12 at 00:32
  • 2
    @apsillers: you're relying on undefined behaviour there. `x` is uninitialized when `(x + 1)` is evaluated. – Jonathan Grynspan Nov 09 '12 at 00:33
  • This compiles on linux too.. Result: 45 45 111 Indeed its a odd case, but is seems it is correct! I guess compiler does the trick. And when those lines converted to assembly they are in an order so when values used, already exist. – Paschalis Nov 09 '12 at 00:33
  • That (`int x = (x + 1);`) is explicitly undefined behaviour; you are incrementing an undefined value which is not guaranteed to be zero. – Jonathan Leffler Nov 09 '12 at 00:33
  • @apsillers Visual Studio gives me a warning for `x = (x + 1)` and random garbage output (as I would expect). – 0x5f3759df Nov 09 '12 at 00:34
  • @JonathanGrynspan I thought I might be, which is why I specified my platform for reference. Does GCC initialize ints to 0, or did I just get lucky? Don't have to answer that here -- I'm drifting off topic. – apsillers Nov 09 '12 at 00:34
  • @apsillers Let me guess ... you placed `int x = (x + 1);` at file (or namespace) scope. Try placing it within a function. – Praetorian Nov 09 '12 at 00:35
  • 2
    @apsillers: You got lucky. On my GCC, the default value is *potato*. – Jonathan Grynspan Nov 09 '12 at 00:49
  • **Related:** http://stackoverflow.com/questions/8595061/is-it-valid-for-a-lambda-to-essentially-close-over-itself – Lightness Races in Orbit Nov 09 '12 at 01:24

4 Answers4

5

From the C++ Standard (C++11, but it wasn't different in C++98/03):

(§ 3.3.2/1) The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), [...] [ Example:

int x = 12;
{ int x = x; }

Here the second x is initialized with its own (indeterminate) value. —end example ]

This applies to user-defined types as well as array-types as well. Notice how the Standard emphasizes that x in the second example is initialised with an indeterminate value. So there is no way to know what value x is initialised with.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
jogojapan
  • 68,383
  • 11
  • 101
  • 131
1

I'll assume you're in some function since you're calling functions and such.

The space for both test and x is allocated on the stack. In theory, the space for these guys should exist before their values are filled in. If we look at the generated assembly (x86 gcc), this is true.

subl    $40, %esp         # Add 40 bytes of memory to the current stack
movl    $0, -20(%ebp)     # Clear test[0] to 0
movl    $0, -16(%ebp)     # Clear test[1] to 0
movl    $45, -20(%ebp)    # Place the value of 45 into test[0]
movl    -20(%ebp), %eax  # Copy that 45 into a register
movl    %eax, -16(%ebp)  # Move that register's value (45) into test[1]
movl    $111, -12(%ebp)  # Assign x to be 111, optimize out the unnecessary duplicate assignment
    ... #continues on to set up and call printf

We can see that 40 bytes are added to the stack. Notice how the addresses of test[0], test[1], and x are all contiguous addresses marked off from %ebp at 4 byte intervals (-20,-16,-12, respectively). Their locations in memory exist and can be accessed without error before they are defined. The compiler here clears them both to 0, although we can see that this is unnecessary. You can delete those two lines and still run fine.

What we can draw from this is that your int test[2] and int x could have any number of funky circular references within themselves and the code will compile - it's just your job to make sure your references grab good data (ie somehow initialized data) and not garbage, which you've done here. This also works with other cases - compile to assembly and check it out for yourself how it's done.

GraphicsMuncher
  • 4,583
  • 4
  • 35
  • 50
0

It's legal, because the declaration of the variable is complete by the time you initialize it. However, the value of test[1] is undefined.

Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
  • How? It's 45. It takes the value at `test[0]` and writes it to `test[1]`. Even if it were a circular reference (ie {test[1],test[0]}), they'd still both just be places on the stack with nonsense values, so test[1]'s junk would go into test[0], and then test[0] would go into test[1], making them both test[1]. – GraphicsMuncher Nov 09 '12 at 00:53
  • @GraphicsMuncher: No... There is no `test[0]` at the time that `test[1]`'s value-to-be is evaluated. And your discussion about the "places on the stack" doesn't make much sense to me either. – Lightness Races in Orbit Nov 09 '12 at 01:22
  • @LightnessRacesinOrbit: The initialization of `test` should be fine. What do you mean there is no `test[0]`? In C++03 there's a sequence point after every initializer (I haven't looked how it is since those were removed), so I'm pretty sure `test[0]` both exists and is initialized once `test[1]` is reached. – GManNickG Nov 09 '12 at 02:50
  • @GManNickG: I don't think it's fair to say that `test[0]` is already initialised by the comma in the middle of the initialiser. – Lightness Races in Orbit Nov 09 '12 at 10:01
  • @LightnessRacesinOrbit: `test` isn't, but `test[0]` is. – GManNickG Nov 09 '12 at 18:42
  • @GManNickG: Until `test`'s initialisation is completed, its value is indeterminate. So we cannot say anything about `test[0]`. – Lightness Races in Orbit Nov 09 '12 at 20:24
0

All the code you have is perfectly legal. There are even circumstances in which you might actually even want to do it, too.

AJMansfield
  • 4,039
  • 3
  • 29
  • 50
  • 1
    Depends what you mean by "legal". Syntactically correct? Sure. Well-defined? Nope. Reading an uninitialized variable is undefined behavior. This only applies to the `x` initialization. The `test` initializer is fine. – GManNickG Nov 09 '12 at 01:14
  • -1: `There are even circumstances in which you might actually even want to do it, too.` No there most certainly absolutely are _not_! – Lightness Races in Orbit Nov 09 '12 at 01:24
  • I mean if you generalized the declarations to other types rather than just `int`, it might actually accomplish something. For instace, if the array's type had pointers to objects in it, you might want to have the second item be a copy of the other, so those pointers would refer to the same objects. For the second one, there might be a circumstance where `operator =` is overloaded by x's type. Although I agree that there is no reason any sane person would want to do it with `int`s. – AJMansfield Nov 09 '12 at 12:16