Code Blocks in C
A code block in the C language is a group of statements and declarations enclosed in curly braces {
and }
.
In this lesson, we will study the relationship between code blocks and variable visibility. We will also examine the concept of shadowing of variables.
Code Blocks
Previously, in the lesson on the if
statement, we introduced the concept of compound statements. We saw that to execute multiple instructions based on the condition of an if
statement, it is necessary to enclose those instructions in curly braces {
and }
.
For example:
if (a > 0) {
printf("a is greater");
printf("than zero");
}
In this case, the printf
statements are enclosed within the curly braces {
and }
. This means they are executed only if the condition of the if
statement is true.
In reality, a compound statement can also contain variable declarations. For example:
if (a > 0) {
int b = 10;
printf("a is greater");
printf("than zero");
}
For this reason, instead of continuing to call this construct a compound statement, from this point forward we will refer to it as a code block.
From a syntactical point of view, the body of a function is also a code block.
Code Block
In the C language, a code block is a group of statements and declarations enclosed in curly braces {
and }
.
The syntax is as follows:
{
declaration1;
declaration2;
...
declarationN;
statement1;
statement2;
...
statementN;
}
In the original C standard, variable declarations must precede any statements.
In C99, it is possible to interleave declarations and statements:
{
declaration1;
statement1;
declaration2;
statement2;
...
declarationN;
statementN;
}
Code Blocks and Scope
Since we can declare variables within a code block, it’s important to understand how these variables are handled by the compiler.
In particular, we need to understand how the compiler manages the visibility or scope of variables declared inside a code block.
By default, the lifetime of a variable declared within a code block is limited to the block itself. This means that a variable declared inside a code block is not visible outside that block. Just like local variables within a function, variables declared inside a code block are local to that block. Formally, a variable declared within a block has automatic storage duration.
For example, consider the following code:
int main() {
int a = 10;
if (a > 0) {
int b = 10;
printf("a is greater");
printf("than zero");
}
printf("b is %d", b);
}
The compiler will raise a compilation error because the variable b
is declared inside a code block and is not visible outside of it.
b.c: In function ‘main’:
b.c:9:27: error: ‘b’ undeclared (first use in this function)
printf("b is %d", b);
^
Code Blocks and Local Variables
A variable declared inside a code block is visible only within that block. For this reason, we refer to them as variables local to the code block.
Nested Code Blocks
In the C language, it is entirely legal to declare a code block within another code block. In this case, we refer to nested code blocks.
For example, consider the following code:
int main() {
int a = 10;
printf("a is %d", a);
{
int b = 10;
printf("b is %d", b);
}
return 0;
}
In this example, we’ve placed a code block inside the body of the main
function. Inside it, we declared the variable b
and printed its value. The variable b
will only be visible within the inner block.
Nested Code Blocks
In the C language, it is possible to declare a code block inside another code block. This is known as a nested code block.
The syntax is as follows:
{
declaration1;
declaration2;
...
declarationN;
statement1;
statement2;
...
statementN;
{
/* Nested code block */
declaration1;
declaration2;
...
declarationN;
statement1;
statement2;
...
statementN;
}
}
Variable Shadowing
In some cases, it can be useful to declare a variable inside a code block using the same name as a variable declared outside that block.
This is allowed in C, but must be used with caution, as it can lead to unexpected results.
When the compiler encounters a variable inside a code block, it searches for a variable with that name within the block first. If it finds one, the inner variable shadows or hides the outer one. This is called shadowing.
For example, consider the following code:
int main() {
int a = 10;
if (a > 0) {
int a = 20;
printf("a is %d", a);
}
printf("a is %d", a);
}
If we compile and run this code, we’ll get the following output:
a is 20
a is 10
In this case, the variable a
declared inside the code block hid the variable a
declared outside the block. So the first printf
prints the value of the inner a
, and the second printf
prints the value of the outer a
.
It’s important to be cautious with variable shadowing. Overusing this mechanism can lead to confusing and error-prone code. It's best to avoid reusing the same variable name inside and outside a block.
Additionally, in C, when a variable is shadowed by one declared in an inner block, the outer variable becomes inaccessible within that block, and there is no way to refer to it. (In C++, which is a superset of C, a mechanism has been introduced to access the outer variable, but it is not available in C.)
Variable Shadowing
Shadowing occurs when a variable is declared with the same name inside a more nested code block.
In this case, the outer variable is shadowed and becomes inaccessible within the inner block.
void f() {
/* Outer local variable */
type name;
/* ... */
{
/* Local variable in the nested block */
/* This variable shadows the previous one */
type name;
/* ... */
}
}
Shadowing and Global Variables
Inside a code block, it is also possible to declare a variable with the same name as a global variable. In this way, the global variable is shadowed.
For example, consider the following code:
int a = 10;
int main() {
if (a > 0) {
int a = 20;
printf("a is %d", a);
}
printf("a is %d", a);
}
If we compile and run this code, we will get the following output:
a is 20
a is 10
Again, the variable a
declared inside the code block has shadowed the global variable a
.
However, in this case, there is a way to access the global variable a
from within the code block. To better understand this, consider the following code:
#include <stdio.h>
/* Global variable */
int a = 10;
int main() {
if (a > 0) {
int a = 20;
printf("a is %d\n", a);
/* Nested code block */
{
extern int a;
printf("a is %d\n", a);
}
}
printf("a is %d\n", a);
}
If we compile and run this code, we will get the following output:
a is 20
a is 10
a is 10
What we did here was to explicitly declare the global variable a
inside the nested block using the extern
keyword. This made it possible to access the global variable a
from within the inner code block.
The extern
keyword, however, can be used only for global variables.
Shadowing and Global Variables
Shadowing of a variable can be bypassed only in the case of a global variable.
To do this, you must use a nested code block in which you declare a variable with the same name as the global one, and use the extern
keyword.
/* Global variable */
type name;
/* ... */
void f() {
/* Local variable that shadows the global one */
type name;
/* ... */
{
/*
* Explicit declaration of the global variable
* In this nested block, the global variable
* becomes accessible
*/
extern type name;
/* ... */
}
}
Summary
In this lesson, we revisited the concept of compound statements and extended it by introducing variable declarations. From now on, we will refer to these as code blocks.
We learned that a code block is a group of instructions that can be executed independently. A code block can be declared inside another code block.
We saw that variables declared within a code block are visible only inside that block. This means they cannot be accessed from outside the block.
Finally, we introduced the concept of shadowing of variables. This phenomenon occurs when a variable declared inside a code block has the same name as a variable declared outside the block. In such cases, the inner variable hides the outer one.
Now that we’ve studied local and global variables and clarified the concept of code blocks, we are ready to dive deeper into the rules that govern variable visibility.