munotes®

Block Structure and Initialization

Chapter Twenty-One

Syllabus topic 2, "Type of operators: Arithmetic operators, relational and logical operators, Increment and Decrement operators, assignment operators, the conditional operator, Assignment operators and expression, Precedence and order of Evaluation Block Structure, Initialization, C Preprocessor"

Pages 99 to 103 of 222

In one line

A block is a pair of braces with declarations and statements inside it, a name declared in a block is visible from its declaration to the closing brace and nowhere else, and initialisation is giving an object a value at the moment the block brings it into existence.

What a block is

{
    declarations and statements
}

That is a block, and it is one statement as far as the language is concerned. That last point is what makes if, while and for work: each of them governs exactly one statement, and a block is how you give them more than one.

Blocks appear in four places:

  1. A function body. Always a block.
  2. The body of an if, else, while, for, do or switch.
  3. Anywhere a statement may go, on its own, to limit the life of a variable.
  4. Nested inside another block, to any depth.

Scope: where a name can be seen

A name declared in a block is visible from the point of its declaration to the closing brace of that block. Not before it, and not after.

#include <stdio.h>

int main(void)
{
    int outer = 1;
    printf("in main, outer is %d\n", outer);

    {
        int inner = 2;
        printf("in the inner block, outer is %d and inner is %d\n",
               outer, inner);
    }

    /* inner does not exist here */
    printf("back in main, outer is %d\n", outer);
    return 0;
}
in main, outer is 1
in the inner block, outer is 1 and inner is 2
back in main, outer is 1

The inner block can see outer, because main's block encloses it. main cannot see inner, because inner's scope ended at the brace.

Moving the printf that mentions inner below the closing brace does not compile. That is not a limitation; it is the whole value of the rule. A variable that cannot be seen cannot be accidentally used, and a reader who reaches the closing brace knows they can stop thinking about it.

Lifetime: how long the object exists

Scope is about the name. Lifetime is about the object, and for an automatic variable they coincide: the object is created when control enters the block and destroyed when control leaves it.

That means a fresh object each time. A variable declared inside a loop body is created and destroyed on every pass, so it cannot remember anything between passes.

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 3; i++) {
        int fresh = 0;          /* new object every pass */
        static int kept = 0;    /* one object, created once */

        fresh = fresh + 1;
        kept = kept + 1;
        printf("pass %d: fresh is %d, kept is %d\n", i, fresh, kept);
    }
    return 0;
}
munotes.in99

Block Structure and Initialization

pass 0: fresh is 1, kept is 1
pass 1: fresh is 1, kept is 2
pass 2: fresh is 1, kept is 3

static changes the lifetime and not the scope. kept still cannot be named outside the loop body; it simply survives out there, waiting.

Shadowing

An inner block may declare a name that already exists outside it. The inner declaration hides the outer one for the rest of that block. This is legal, it is occasionally useful, and it is usually a mistake.

#include <stdio.h>

int value = 100;               /* global */

int main(void)
{
    int value = 10;            /* hides the global */
    printf("in main, value is %d\n", value);

    {
        int value = 1;         /* hides main's */
        printf("in the inner block, value is %d\n", value);
    }

    printf("back in main, value is %d again\n", value);
    return 0;
}
in main, value is 10
in the inner block, value is 1
back in main, value is 10 again

Three objects called value exist at once and each printf reaches the nearest one. Nothing is wrong with that program and it is still a bad idea: a reader has to count braces to know which value a line means, and a typing mistake that was meant for one will silently hit another. Give the inner variable a different name.

The one honest use of shadowing is a loop counter: for (int i = ...) inside a function that also has an i somewhere far away. Even then, a different name is clearer.

for and its own scope

Since C99, a variable declared in a for header belongs to the loop.

for (int i = 0; i < n; i++) {
    ...
}
/* i does not exist here */

This is the form to use. It says the counter is the loop's business and nobody else's, and it means two loops in one function can both use i without interfering. Chapter 28.

Initialisation

The general rule: an object may be given a value at the point it is created, and for some kinds of object that is the only way to give it one.

A scalar: covered in chapter 11.

int marks = 75;
double rate = 8.5;
char grade = 'A';

An array: a braced list. Chapter 36 is arrays proper; this is the initialisation rule.

#include <stdio.h>

int main(void)
{
    int a[5] = {10, 20, 30, 40, 50};   /* all five given            */
    int b[5] = {10, 20};               /* the rest are set to 0     */
    int c[5] = {0};                    /* every element 0           */
    int d[] = {1, 2, 3};               /* size counted for you: 3   */
    int e[5] = {[4] = 99, [0] = 11};   /* C99: by position, any order */

    printf("a: "); for (int i = 0; i < 5; i++) printf("%d ", a[i]);
    printf("\nb: "); for (int i = 0; i < 5; i++) printf("%d ", b[i]);
    printf("\nc: "); for (int i = 0; i < 5; i++) printf("%d ", c[i]);
    printf("\nd has %zu elements: ", sizeof d / sizeof d[0]);
    for (int i = 0; i < 3; i++) printf("%d ", d[i]);
    printf("\ne: "); for (int i = 0; i < 5; i++) printf("%d ", e[i]);
    printf("\n");
    return 0;
}
munotes.in100

Block Structure and Initialization

a: 10 20 30 40 50
b: 10 20 0 0 0
c: 0 0 0 0 0
d has 3 elements: 1 2 3
e: 11 0 0 0 99

Four rules come out of that:

  1. A partly initialised array has the rest set to zero. So int c[5] = {0}; zeroes the whole array, and it is the standard idiom for doing so.
  2. An array with no initialiser at all is not zeroed if it is automatic. int a[5]; inside a function holds rubbish, exactly as a single int does.
  3. int d[] = {1, 2, 3}; lets the compiler count. sizeof d / sizeof d[0] then recovers the count, and that expression is worth memorising.
  4. Designated initialisers, [4] = 99, were added in C99 and let you give elements by position in any order, with everything else zero.

A string: chapter 12's rule, which is the array rule with a terminator.

char name[] = "Anita";      /* 6 bytes, counted for you */
char buf[20] = "Anita";     /* 20 bytes, 14 of them zero */

You may initialise a character array from a string literal and you may not assign one. char s[6]; s = "Anita"; does not compile. Initialisation and assignment are different events, and only the first can fill an array.

const and initialisation

A const object can only ever be given its value at initialisation, because assignment to it is not allowed. That is the point of it.

const double PI = 3.14159;     /* the only chance to set it */

What this does NOT mean

A block is not a function. Both use braces. A function has a name, parameters and a return type, and is called; a block is a piece of a function.

Scope is not lifetime. Scope is where a name is visible; lifetime is how long the object exists. A static variable in a block has block scope and program lifetime.

Entering a block does not clear it. An automatic variable with no initialiser holds whatever was in that memory, every time the block is entered.

munotes.in101

Block Structure and Initialization

Shadowing is not an error. It compiles, and that is the problem.

A partly initialised array is not partly undefined. The elements you did not give are zero. This is the one case where C does zero things for you, and it applies only when there is an initialiser present.

int a[5] = {1}; does not set every element to 1. It sets the first to 1 and the rest to 0. int a[5] = {0}; happens to work as "all zero" only because the fill value is also zero.

Quick revision

  • A block is braces containing declarations and statements, and counts as one statement.
  • A name declared in a block is visible from its declaration to the closing brace.
  • An automatic object is created on entry to its block and destroyed on exit, and is not zeroed.
  • static in a block changes lifetime to the whole program; scope is unchanged.
  • An inner declaration of the same name shadows the outer one. Legal, and to be avoided.
  • Since C99, for (int i = ...) scopes the counter to the loop.
  • An array is initialised with a braced list; elements left out are zero.
  • int a[5] = {0}; zeroes an array; int a[5]; in a function does not.
  • int d[] = {1,2,3}; counts for you, and sizeof d / sizeof d[0] recovers the count.
  • C99 designated initialisers: int e[5] = {[4] = 99};.
  • An array can be initialised from a string literal but never assigned one.
  • A const object must be given its value at initialisation.

Test yourself

1. What is the scope of a variable declared inside an if body?

From its declaration to the closing brace of that body. It cannot be named after the if.

2. Does a variable declared inside a loop body keep its value between passes?

No. It is created and destroyed on each pass. Declare it before the loop, or make it static, if it must persist.

3. What is shadowing? Is it an error?

Declaring a name in an inner block that already exists in an enclosing scope, so that the inner one hides the outer for the rest of the block. It is legal and usually a mistake.

4. After int a[5] = {1, 2}; what is a[4]?

  1. Elements not given in an initialiser are set to zero.

5. After int a[5]; inside a function, what is a[4]?

Unspecified. With no initialiser an automatic array is not zeroed.

6. How do you find the number of elements of int d[] = {4, 8, 15, 16};?

sizeof d / sizeof d[0], which is 4. That works only where the array itself is in scope, not on a parameter, which chapter 41 explains.

munotes.in102

Block Structure and Initialization

7. Why must a const object be initialised rather than assigned?

Because assignment to a const object is not allowed, so initialisation is the only point at which it can be given a value.

What can be asked on this, and how to answer it

"What is a block? Explain block structure in C." Define it as braces enclosing declarations and statements, counting as a single statement. Say where blocks appear, give the scope rule, and give a nested example showing that the inner block sees the outer names and not the reverse. Mention that this is why if and while can govern several statements.

"Explain scope and lifetime with an example." Scope is the region of the program where a name is visible; lifetime is the period for which the object exists. For an automatic variable they coincide with the block. Give the static case as the one where they differ, with the three-pass loop program.

"What is variable shadowing?" An inner declaration hiding an outer name of the same spelling for the extent of the inner block. Give the three-level value program, and say that it is legal and should be avoided because a reader must count braces to know which object a line means.

"How is an array initialised? What happens to elements not given a value?" With a braced list at the point of declaration. Elements omitted are set to zero, provided an initialiser is present; with no initialiser at all an automatic array is not zeroed. Give int a[5] = {0}; as the idiom for zeroing, and the empty-bracket form that lets the compiler count.

munotes.in103

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Report or request
Done!