5

Why does the following compile without an error?:

int main()
{
    int x = x;  //I thought this should cause an error
    return 0;
}

Where in the standards is it explained why this is allowed?

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Are you interested in the answer for c++ or for c? It is possible that they are different, considering they have different standards. – horns Apr 08 '15 at 21:39
  • 2
    It is undefined behavior. – Maroun Apr 08 '15 at 21:39
  • One more good reason to turn on compiler warnings: `-Wall -Werror` are your friends. – chqrlie Apr 08 '15 at 22:44
  • For C++ see [Does initialization entail lvalue-to-rvalue conversion? Is int x = x; UB?](http://stackoverflow.com/q/14935722/1708801) and [Has C++ standard changed with respect to the use of indeterminate values and undefined behavior in C++14](http://stackoverflow.com/q/23415661/1708801)? Both question offer a very detailed discussion of this issue. – Shafik Yaghmour Apr 09 '15 at 01:48
  • 1
    @ShafikYaghmour I knew it was a bad idea to allow the dual tag question :X Perhaps this question should be edited to be C++-only (and closed as duplicate), and a new C question posted – M.M Apr 09 '15 at 03:51

4 Answers4

6

This question has a slightly different answer in C than in C++.

In both cases, int x = x; tries to initialize x with itself.


In C++: [dcl.init]/12 (N3936) says that any evaluation of an object with indeterminate value causes undefined behaviour, except for certain cases involving unsigned char. In fact there is an Example:

int f(bool b) {
    unsigned char c;
    unsigned char d = c; // OK, d has an indeterminate value
    int e = d;        // undefined behavior
    return b ? d : 0; // undefined behavior if b is true
}

In C: It is more complicated. This is very similar to the behaviour of int b; foo(b - b); which is covered in full here.

I won't repeat that text but the conclusions are, in C11:

  • int a = a; &a; causes UB if and only if the system has trap representations for int
  • int a = a;, with no subsequent occurrence of &a, causes UB.

Historical note: In C90 this caused UB. In C99 the trap representation was introduced, and in C11 the possibility of register trap was introduced (for Itanium). The C++ standard doesn't deal with trap representations at all, it seems underspecified in the case of things like bitwise operators generating negative zero.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    Pascal provides a very nice summary of the evolution of indeterminate values in C in [Reading indeterminate contents might as well be undefined](http://blog.frama-c.com/index.php?post/2013/03/13/indeterminate-undefined) – Shafik Yaghmour Apr 09 '15 at 02:39
  • Hmm - shouldn't declaring 'a' with 'volatile' keyword do the same job as a request of the variable address (&a)? – AnArrayOfFunctions Apr 09 '15 at 22:13
  • @FISOCPP the standard doesn't say that – M.M Apr 09 '15 at 22:43
3

C++14 draft N4140 [basic.scope.pdecl]/1:

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below. [ Example:

unsigned char x = 12;
{ unsigned char x = x; }

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

Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • Most of what you are quoting is a note which is not normative and it does not really explain whether this is undefined behavior or not. See [my comment above](http://stackoverflow.com/questions/29525797/can-a-variable-be-used-while-being-declared#comment47210802_29525797) for two questions that answers this question for C++ in detail for both pre C++14 and C++14 and forward. – Shafik Yaghmour Apr 09 '15 at 02:00
  • @ShafikYaghmour: The question isn't whether this is undefined behavior, it is whether the variable can be used in its own initialization. There are plenty of cases which use the variable without using its value, e.g. `void* p = &p;` so it is a perfectly reasonable question. – Ben Voigt Apr 09 '15 at 02:22
  • @BenVoigt in the C++ case this example is clearly undefined behavior and not saying so is a poor answer IMHO. Sure there are caveats but none of them are explained. The way modern optimizing compilers work we need to be very clear on what is and what is not UB b/c the optimizers can do very aggressive things with code that it believes invokes UB. – Shafik Yaghmour Apr 09 '15 at 02:24
  • Note that C++14 amended this example to use `unsigned char` since the `int` case invokes UB. – Shafik Yaghmour Apr 09 '15 at 02:37
3

The answer for C++ - I am quoting C++11 standard:

3.3.3.1:

A name declared in a block (6.3) is local to that block; it has block scope. Its potential scope begins at its point of declaration (3.3.2) and ends at the end of its block.

And the point of declaration is defined in 3.3.2 as

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below.

[ Example:

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

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

Obviously, using the value of x before it is initialised is undefined behaviour.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
0

There really are two parts to this question, the one asked:

Can a variable be used while being declared?

where there answer clearly is Yes, due to the point of declaration rule quoted in other answers.

And, equally important but not asked:

What usages of a variable during its declaration are safe?

Well, within the initializer, the variable has not yet completed initialization (a matter of object lifetime), and in fact construction hasn't yet even started. The object lifetime rules (section 3.8 of the Standard) state that some but not all operations are permitted on such a variable:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a pointer refers to allocated storage, and using the pointer as if the pointer were of type void*, is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:

  • the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,
  • the pointer is used to access a non-static data member or call a non-static member function of the object, or
  • the pointer is implicitly converted to a pointer to a virtual base class, or
  • the pointer is used as the operand of a static_cast, except when the conversion is to pointer to cv void, or to pointer to cv void and subsequently to pointer to either cv char or cv unsigned char, or
  • the pointer is used as the operand of a dynamic_cast.

Effectively, for types with non-trivial initialization, the memory location doesn't contain an object yet, so it has no dynamic type, and attempting to access it as any type except char or unsigned char immediately falls afoul of strict aliasing.

For types with trivial initialization, including int, an object exists as soon as properly aligned storage is acquired. But if that storage has automatic or dynamic storage duration, the value is indeterminate until the variable is written to. This rule from section 8.5 applies:

If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced. [ Note: Objects with static or thread storage duration are zero-initialized, see 3.6.2. — end note ] If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:

and all the exceptional cases listed are specific to unsigned char.

At first glance, this rule appears not to apply, because an initializer is specified. However, during evaluation of the initializer, we are exactly in the case that "When storage for an object with automatic or dynamic storage duration is obtained" where the rule does apply.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720