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?
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?
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.
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 ]
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.
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 tocv
void
, or to pointer tocv
void
and subsequently to pointer to eithercv
char
orcv
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.