In the following code, why is the variable i not assigned the value 1?
#include <stdio.h>
int main(void)
{
int val = 0;
switch (val) {
int i = 1; //i is defined here
case 0:
printf("value: %d\n", i);
break;
default:
printf("value: %d\n", i);
break;
}
return 0;
}
When I compile, I get a warning about i not being initialized despite int i = 1; that clearly initializes it
$ gcc -Wall test.c
warning: ‘i’ is used uninitialized in this function [-Wuninitialized]
printf("value %d\n", i);
^
If val = 0, then the output is 0.
If val = 1 or anything else, then the output is also 0.
Please explain to me why the variable i is declared but not defined inside the switch. The object whose identifier is i exists with automatic storage duration (within the block) but is never initialized. Why?
According to the C standard (6.8 Statements and blocks), emphasis mine:
3 A block allows a set of declarations and statements to be grouped
into one syntactic unit. The initializers of objects that have
automatic storage duration, and the variable length array declarators
of ordinary identifiers with block scope, are evaluated and the values
are stored in the objects (including storing an indeterminate value
in objects without an initializer) each time the declaration is
reached in the order of execution, as if it were a statement, and
within each declaration in the order that declarators appear.
And (6.8.4.2 The switch statement)
4 A switch statement causes control to jump to, into, or past the
statement that is the switch body, depending on the value of a
controlling expression, and on the presence of a default label and the
values of any case labels on or in the switch body. A case or default
label is accessible only within the closest enclosing switch
statement.
Thus the initializer of variable i is never evaluated because the declaration
switch (val) {
int i = 1; //i is defined here
//...
is not reached in the order of execution due to jumps to case labels and like any variable with the automatic storage duration has indeterminate value.
See also this normative example from 6.8.4.2/7:
EXAMPLE In the artificial program fragment
switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17; /* falls through into default code */
default:
printf("%d\n", i);
}
the object whose identifier is i exists with
automatic storage duration (within the block) but is never
initialized, and thus if the controlling expression has a nonzero
value, the call to the printf function will access an indeterminate
value. Similarly, the call to the function f cannot be reached.
In the case when val is not zero, the execution jumps directly to the label default. This means that the variable i, while defined in the block, isn't initialized and its value is indeterminate.
6.8.2.4 The switch statement
A switch statement causes control to jump to, into, or past the statement that is the
switch body, depending on the value of a controlling expression, and on the presence of a
default label and the values of any case labels on or in the switch body. A case or
default label is accessible only within the closest enclosing switch statement.
Indeed, your i is declared inside the switch block, so it only exists inside the switch. However, its initialization is never reached, so it stays uninitialized when val is not 0.
It is a bit like the following code:
{
int i;
if (val==0) goto zerovalued;
else goto nonzerovalued;
i=1; // statement never reached
zerovalued:
i = 10;
printf("value:%d\n",i);
goto next;
nonzerovalued:
printf("value:%d\n",i);
goto next;
next:
return 0;
}
Intuitively, think of raw declaration like asking the compiler for some location (on the call frame in your call stack, or in a register, or whatever), and think of initialization as an assignment statement. Both are separate steps, and you could look at an initializing declaration in C like int i=1; as syntactic sugar for the raw declaration int i; followed by the initializing assignment i=1;.
(actually, things are slightly more complex e.g. with int i= i!=i; and even more complex in C++)
Line for initialization of i variable int i = 1; is never called because it does not belong to any of available cases.
The initialization of variables with automatic storage durations is detailed in C11 6.2.4p6:
For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.) If the block is entered recursively, a new instance of the object is created each time. The initial value of the object is indeterminate. If an initialization is specified for the object, it is performed each time the declaration or compound literal is reached in the execution of the block; otherwise, the value becomes indeterminate each time the declaration is reached.
I.e. the lifetime of i in
switch(a) {
int i = 2;
case 1: printf("%d",i);
break;
default: printf("Hello\n");
}
is from { to }. Its value is indeterminate, unless the declaration int i = 2; is reached in the execution of the block. Since the declaration is before any case label, the declaration cannot be ever reached, since the switch jumps to the corresponding case label - and over the initialization.
Therefore i remains uninitialized. And since it does, and since it has its address never taken, the use of the uninitialized value to undefined behaviour C11 6.3.2.1p2:
[...] If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.
(Notice that the standard itself here words the contents in the clarifying parenthesis incorrectly - it is declared with an initializer but the initializer is not executed).
Related
I have found multiple questions regarding the subject of defining variables inside a switch construct, but I have not yet found a clear answer to this question.
Chapter 5.3.2 of the book C++ Primer says the following:
As we’ve seen, execution in a switch can jump across case labels. When execution jumps to a particular case, any code that occurred inside the switch before that label is ignored.
Considering this information, I do not understand why the example below is legal. If control jumps to the false case, it should ignore the true case. This means that, assigning to i should be illegal, because it was never declared. Why is this construct legal?
case true:
int i;
break;
case false:
i = 42;
break;
Declaration is a compile-time thing, and what happens at runtime is irrelevant to that fact. i is visible at any point within the same or child scope after its declaration.
There is nothing that causes a scope change between the two cases, so i remains visible in the false case regardless of whether the true case executed.
This is why you may see anonymous blocks ({ }) used to artificially constrain scope in switch cases. It's to prevent exactly this potential issue (though in this case it's not an issue).
case true: {
int i;
break;
} // Closing this block causes the end of i's lifetime.
case false: {
i = 42; // Compile-time error; i is no longer in scope.
break;
}
Note that your code becomes illegal just by initializing i. Jumps cannot cross over initialization in either direction.
case true:
int i = 0;
break;
case false: // error: jump to case label crosses initialization
i = 42;
break;
Also, any variable of a type that is not trivial cannot have a lifetime spanning cases even if it is not explicitly initialized.
case true:
std::string i;
break;
case false: // error: jump to case label crosses initialization
i = "42";
break;
The fix in this case is to use anonymous blocks to constrain the scope the declaration of i to not span multiple cases.
The relevant standardese:
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps* from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer.
-- C++14 (N4296) [stmt.dcl.3]
The footnote (*) regarding jumps:
The transfer from the condition of a switch statement to a case label is considered a jump in this respect.
The compiler throws no warnings or errors for the following code. Is the meaning of the const qualifier being abused? Obviously I cannot reassign it later in the same loop iteration but it does seem to reassign it after each iteration.
Sample code:
for(int i = 0; i < 10; ++i)
{
const int constant = i;
}
You aren't re-initializing it, you're just initializing it in each loop iteration*. Formally there is a new int being created and destroyed in each loop iteration, although the compiler can do whatever it wants as long as it seems to behave that way.
* You can't really "re-initialize" things in C++, initialization only happens once in the lifetime of an object
If to follow the C Standard then (6.2.4 Storage durations of objects)
1 An object has a storage duration that determines its lifetime.
There are four storage durations: static, thread, automatic, and
allocated. Allocated storage is described in 7.22.3.
and
5 An object whose identifier is declared with no linkage and without
the storage-class specifier static has automatic storage duration,
as do some compound literals. The result of attempting to indirectly
access an object with automatic storage duration from a thread other
than the one with which the object is associated is
implementation-defined.
6 For such an object that does not have a variable length array type,
its lifetime extends from entry into the block with which it is
associated until execution of that block ends in any way. (Entering an
enclosed block or calling a function suspends, but does not end,
execution of the current block.) If the block is entered
recursively, a new instance of the object is created each time. The
initial value of the object is indeterminate. If an initialization is
specified for the object, it is performed each time the declaration or
compound literal is reached in the execution of the block; otherwise,
the value becomes indeterminate each time the declaration is reached
And at last (6.8.5 Iteration statements)
5 An iteration statement is a block whose scope is a strict subset of
the scope of its enclosing block. The loop body is also a block
whose scope is a strict subset of the scope of the iteration
statement.
Thus in this loop statement
for(int i = 0; i < 10; ++i)
{
const int constant = i;
}
the body of the loop is a block. The variable constant has the automatic storage duration. A new instance of the variable is created each time the block is executed recursively.
In C++ you may add storage class specifier static. In this case the variable indeed will be initialized only once because it has the static storage duration ( In C you may not do the same because the variable must be initialized by a constant expression).
Here is a demonstrative program
#include <iostream>
int main()
{
for ( int i = 0; i < 10; ++i )
{
static const int constant = i;
std::cout << "constant = " << constant << std::endl;
}
return 0;
}
Its output is
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
constant = 0
You're not actually reinitializing here. You're creating a new variable each time through the loop.
constant is local to the block inside of the loop. When the block finishes on a given iteration and control goes back to the for, constant goes out of scope and therefore no longer exists. When the for starts the next iteration of the loop, a new instance of constant is created and initialized.
This variable gets initialized and destroyed on each iteration because it's local to the loop.
In C++ the variable definitions became an operation, which they weren't in C up until that point. That change was made so that you could place the loop variable definition inside the for loop, e.g.
for (int i = 0; i < N; i++) {
printf("%d", i);
}
My question is what is the value of the variable definition operation, e.g. in which case what conditional statement will be executed in this example:
if (int i = N) {
printf("yes");
} else {
printf("no");
}
If the value of i after the initialization is not equal to zero then the if substetement will be executed. Otherwise the else substatement will be executed.
More precisely (the C++ Standard, 6.4 Selection statements)
4 The value of a condition that is an initialized declaration in a
statement other than a switch statement is the value of the declared
variable contextually converted to bool
And (4.12 Boolean conversions)
1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer
to member type can be converted to a prvalue of type bool. A zero
value, null pointer value, or null member pointer value is converted
to false; any other value is converted to true.
Consider a simple example
#include <iostream>
#include <cstring>
//...
char nickname[] = "mgn1993";
if ( char *p = std::strchr( nickname, 'm' ) ) *p = 'M';
std::cout << nickname << std::endl;
In this code fragment variable p is only needed inside the substatement of the if statement. There is no great sense to declare the variable in the outer scope.
You can use this as a shorthand for evaluating an expression, and use its return value inside the if block. e.g.
if (int i = calculateSomething()) {
// do something with i
}
which is equivalent to
int i = calculateSomething();
if (i) {
// do something with i
}
except that i's scope is restricted to the if block
The value of a variable definition is the variable itself.
int i = 0; // is 0, and is therefore false
int j = 5; // is 5, and is therefore true
The scope of a variable definition is the block it applies to.
So:
if(int i = returnSomething()) {
// This point is reached if returnSomething() did not return 0
// i is defined in this block and can be used.
}
// i is not defined at that point, its scope being limited to the if block above
In the provided example, the output would be "yes", if N has a non-zero value (which would evaluate to a boolean true in C++). There is no real value to the given example, as you can very easily substitute the entire assignment with 'N' and achieve the same effect.
Maybe there is some strange fringe case where we very much need to both use and be able to adjust the value contained in N ONLY if N is non-zero and we simultaneously need to be absolutely assured the scope is restricted to the if-statement, but this seems an unlikely scenario.
That said, declaring a variable for instance in a for loop certainly has its advantages. For starters the scope is limited to the loop, some compilers optimize specifically for it, potentially cleaner code, etc.
I have a program with a switch statement similar to this:
switch(n)
{
case 0:
/* stuff */
break;
int foo;
case 1:
foo = 5;
break;
case 2:
foo = 6;
break;
}
Notice the int foo; between case 0 and case 1. This statement is unreachable: if you walk through the program, you'll never step over it.
This compiles without warnings or errors with Clang, but it seemed to be jacked up when I ran it (though that could be due to other causes).
Is it well-defined behavior to declare a variable in an unreachable statement and use it in reachable statements, and is it going to work?
It is well-defined behavior as long as the variable has trivial construction, and has (approximately)
the same effect as if the variable was declared in a larger scope.
If any initialization is needed, you'll get an error.
section 6.7 says
It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formed unless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer.
Consider the following piece of code:
int l;
int k;
for (int i=0; i < 2; ++i)
{
int j;
if (i == 0) l = j;
if (i == 1) k = j;
}
assert (l == k);
Does the assertion hold? The interesting piece is if the uninitialized variable can take different values in different iterations.
Some playing around with LLVM suggests that the assertion actually does hold: is this actually guaranteed by the standard or if it is undefined and that it happens the way the LLVM compiler implements it?
As to what you're trying to do: the value of j is indeterminate. It's just whatever happens to be on the stack when it's declared, so the assertion does not necessarily hold.
edit: it was pointed out that, since j is likely on the same place on the stack every time it's allocated, what is the expected behavior of the value?
The fact that it's the same is just a fact of the implementation. The standard states:
6.2.4 For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which
it is associated until execution of that block ends in any way.
(Entering an enclosed block or calling a function suspends, but does
not end, execution of the current block.) If the block is entered
recursively, a new instance of the object is created each time. The
initial value of the object is indeterminate. If an initialization is
specified for the object, it is performed each time the declaration is
reached in the execution of the block; otherwise, the value becomes
indeterminate each time the declaration is reached.
It's indeterminate.
edit 2: that was the C standard. From C++:
6.7 Variables with automatic storage duration (3.7.2) are initialized each
time their declaration-statement is executed. Variables with
automatic storage duration declared in the block are destroyed on exit
from the block (6.6).