Chapter 6. Statements

A statement specifies one or more actions to be performed such as assigning a value to a variable, passing control to a function, or jumping to another statement. The sum total of all the statements in a program determines what the program does.

Jumps and loops are statements that control the flow of the program. Except when those control statements result in jumps, statements are executed sequentially; that is, in the order in which they appear in the program.

Expression Statements

An expression statement is an expression followed by a semicolon:

[expression] ;

In an expression statement, the expression—whether an assignment or another operation—is evaluated for the sake of its side effects. Following are some typical expression statements :

y = x;                         // An assignment
sum = a + b;                   // Calculation and assignment
++x;
printf("Hello, world\n");      // A function call

The type and value of the expression are irrelevant, and are discarded before the next statement is executed. For this reason, statements such as the following are syntactically correct, but not very useful:

100;
y < x;

If a statement is a function call and the return value of the function is not needed, it can be discarded explicitly by casting the function as void:

char name[32];
/* ... */
(void)strcpy( name, "Jim" );   // Explicitly discard
                               // the return value.

A statement can also consist of a semicolon alone; this is called a null statement. Null statements are necessary in cases where syntax requires a statement but the program should not perform any action. In the following example, a null statement forms the body of a for loop:

for ( i = 0; s[i] != '\0'; ++i ) // Loop conditions
  ;                              // A null statement

This code sets the variable i to the index of the first null character in the array s, using only the expressions in the head of the for loop.

Block Statements

A compound statement, called a block for short, groups a number of statements and declarations together between braces to form a single statement:

{ [list of declarations and statements] }

Unlike simple statements, block statements are not terminated by a semicolon. A block is used wherever the syntax calls for a single statement but the program’s purpose requires several statements. For example, you can use a block statement in an if statement or when more than one statement needs to be repeated in a loop:

{  double result = 0.0, x = 0.0;   // Declarations
   static long status = 0;
   extern int limit;

   ++x;                            // Statements
   if ( status == 0 )
   {                               // New block
      int i = 0;
      while ( status == 0 && i < limit )
      {  /* ... */  }              // Another block
   }
   else
   {  /* ... */  }                 // And yet another block
}

The declarations in a block are usually placed at the beginning, before any statements. However, C99 allows declarations to be placed anywhere.

Names declared within a block have block scope; in other words, they are visible only from their declaration to the end of the block. Within that scope, such a declaration can also hide an object of the same name that was declared outside the block. The storage duration of automatic variables is likewise limited to the block in which they occur. This means that the storage space of a variable not declared as static or extern is automatically freed at the end of its block statement. For a full discussion of scope and storage duration, see Chapter 11.

Loops

Use a loop to execute a group of statements, called the loop body, more than once. In C, you can introduce a loop using one of three iteration statements: while, do…while, and for.

In each of these statements, the number of iterations through the loop body is controlled by a condition, the controlling expression. This is an expression of a scalar type; that is, an arithmetic expression or a pointer. The loop condition is true if the value of the controlling expression is not equal to 0; otherwise, it is considered false.

The statements break and continue are used to jump out or back to the top of a loop before the end of an iteration. They are described in “Unconditional Jumps”.

while Statements

A while statement executes a statement repeatedly as long as the controlling expression is true:

while (expression ) statement

The while statement is a top-driven loop: first, the loop condition (i.e., the controlling expression) is evaluated. If it yields true, the loop body is executed, and then the controlling expression is evaluated again. If the condition is false, program execution continues with the statement that follows the loop body.

Syntactically, the loop body consists of one statement. If several statements are required, they are grouped in a block. Example 6-1 shows a simple while loop that reads in floating-point numbers from the console and accumulates a running total of them.

Example 6-1. A while loop
/* Read in numbers from the keyboard and
 * print out their average.
 * -------------------------------------- */
#include <stdio.h>
int main()
{
   double x = 0.0, sum = 0.0;
   int count = 0;

   printf( "\t--- Calculate Averages ---\n" );
   printf( "\nEnter some numbers:\n"
           "(Type a letter to end your input)\n" );
   while ( scanf( "%lf", &x ) == 1 )
   {
      sum += x;
      ++count;
   }
   if ( count == 0 )
     printf( "No input data!\n" );
   else
     printf( "The average of your numbers is %.2f\n", sum/count );
   return 0;
}

In Example 6-1, the controlling expression:

scanf( "%lf", &x ) == 1

is true as long as the user enters a decimal number. As soon as the function scanf() is unable to convert the string input into a floating-point number—when the user types the letter q, for example—scanf() returns the value 0 (or -1 for EOF, if the end of the input stream was reached or an error occurred). The condition is then false, and execution continues at the if statement that follows the loop body.

for Statements

Like the while statement, the for statement is a top-driven loop, but with more loop logic contained within the statement itself:

for ( [expression1]; [expression2]; [expression3] )
   statement

The three actions that need to be executed in a typical loop are specified together at the top of the loop body:

expression1 (initialization)

Evaluated only once, before the first evaluation of the controlling expression, to perform any necessary initialization.

expression2 (controlling expression)

Tested before each iteration. Loop execution ends when this expression evaluates to false.

expression3 (adjustment)

An adjustment, such as the incrementation of a counter, performed after each loop iteration and before expression2 is tested again.

Example 6-2 shows a for loop that initializes each element of an array.

Example 6-2. Using a for loop to initialize an array
#define ARR_LENGTH  1000
/* ... */
long arr[ARR_LENGTH];
int i;
for ( i = 0; i < ARR_LENGTH; ++i )
    arr[i] = 2*i;

Any of the three expressions in the head of the for loop can be omitted. This means that its shortest possible form is:

for ( ; ; )

A missing controlling expression is considered to be always true, and so defines an infinite loop.

The following form, with no initializer and no adjustment expression, is equivalent to while ( expression ):

for ( ;expression; )

In fact, every for statement can also be rewritten as a while statement, and vice versa. For example, the complete for loop in Example 6-2 is equivalent to the following while loop:

i = 0;                       // Initialize the counter
while ( i < ARR_LENGTH )     // The loop condition
{
    arr[i] = 2*i;
    ++i;                     // Increment the counter
}

for is generally preferable to while when the loop contains a counter or index variable that needs to be initialized and then adjusted after each iteration.

In ANSI C99, a declaration can also be used in place of expression1. In this case, the scope of the variable declared is limited to the for loop. For example:

for ( int i = 0; i < ARR_LENGTH; ++i )
    arr[i] = 2*i;

The variable i declared in this for loop, unlike that in Example 6-2, no longer exists after the end of the for loop.

The comma operator is often used in the head of a for loop in order to assign initial values to more than one variable in expression1, or to adjust several variables in expression3. For example, the function strReverse() shown here uses two index variables to reverse the order of the characters in a string:

void strReverse( char* str)
{
  char ch;
  for ( size_t i = 0, j = strlen(str)-1;  i < j;  ++i, --j )
    ch = str[i],  str[i] = str[j],  str[j] = ch;
}

The comma operator can be used to evaluate additional expressions in places where only one expression is permitted. See “Other Operators” for a detailed description of the comma operator.

do…while Statements

The do…while statement is a bottom-driven loop:

do statement while ( expression );

The loop body statement is executed once before the controlling expression is evaluated for the first time. Unlike the while and for statements, do…while ensures that at least one iteration of the loop body is performed. If the controlling expression yields true, then another iteration follows. If false, the loop is finished.

In Example 6-3, the functions for reading and processing a command are called at least once. When the user exits the menu system, the function getCommand() returns the value of the constant END.

Example 6-3. do…while
// Read and carry out an incoming menu command.
// --------------------------------------------
int getCommand( void );
void performCommand( int cmd );
#define END 0
/* ... */
do
{
  int command = getCommand();  // Poll the menu system.
  performCommand( command );   // Execute the command received.
} while ( command != END );

Example 6-4 shows a version of the standard library function strcpy(), with just a simple statement rather than a block in the loop body. Because the loop condition is tested after the loop body, the copy operation includes the string terminator '\0'.

Example 6-4. A strcpy() function using do…while
// Copy string s2 to string s1.
// ----------------------------
char *strcpy( char* restrict s1, const char* restrict s2 )
{
  int i = 0;
  do
     s1[i] = s2[i];           // The loop body: copy each character
  while ( s2[i++] != '\0' );  // End the loop if we just copied a '\0'.
  return s1;
}

Nested Loops

A loop body can be any simple or block statement, and may include other loop statements. Note that a break or continue statement that occurs in a nested loop only jumps to the end or the beginning of the loop that immediately contains it (see “Unconditional Jumps”).

Example 6-5 is an implementation of the bubble-sort algorithm using nested loops. The inner loop in this algorithm inspects the entire array on each iteration, swapping neighboring elements that are out of order. The outer loop is reiterated until the inner loop finds no elements to swap. After each iteration of the inner loop, at least one element has been moved to its correct position. Hence the remaining length of the array to be sorted, len, can be reduced by one.

Example 6-5. Nested loops in the bubble-sort algorithm
// Sort an array of float in ascending order
// using the bubble-sort algorithm.
// -----------------------------------------
void bubbleSort( float arr[], int len ) // The array arr and
{                                       // its length len.
  int isSorted = 0;
  do
  {
     float temp;             // Holder for values being swapped.
     isSorted = 1;
     --len;
     for ( int i = 0;  i < len;  ++i )
       if ( arr[i] > arr[i+1] )
       {
          isSorted = 0;      // Not finished yet.
          temp = arr[i];     // Swap adjacent values.
          arr[i] = arr[i+1];
          arr[i+1] = temp;
       }
   } while ( !isSorted );
}

Note that the automatic variables temp, declared in the do…while loop, and i, declared in the head of the for loop, are created and destroyed again on each iteration of the outer loop.

Selection Statements

A selection statement can direct the flow of program execution along different paths depending on a given condition. There are two selection statements in C: if and switch.

if Statements

An if statement has the following form:

if (expression ) statement1 [ else statement2 ]

The else clause is optional. The expression is evaluated first, to determine which of the two statements is executed. This expression must have a scalar type. If its value is true—that is, not equal to 0—then statement1 is executed. Otherwise, statement2, if present, is executed.

The following example uses if in a recursive function to test for the condition that ends its recursion:

// The recursive function power() calculates
// integer powers of floating-point numbers.
// -----------------------------------------
double power( double base, unsigned int exp )
{
   if ( exp == 0 ) return 1.0;
   else return base * power( base, exp-1 );
}

If several if statements are nested, then an else clause always belongs to the last if (on the same block nesting level) that does not yet have an else clause:

if ( n > 0 )
   if ( n % 2 == 0 )
      puts( "n is positive and even" );
   else                                 // This is the alternative
      puts( "n is positive and odd" );  // to the *last* if

An else clause can be assigned to a different if by enclosing the last if statement that should not have an else clause in a block:

if ( n > 0 )
{
  if ( n % 2 == 0 )
     puts( "n is positive and even" );
}
else                                  // This is the alternative
   puts( "n is negative or zero" );   // to the *first* if

To select one of more than two alternative statements, if statements can be cascaded in an else if chain. Each new if statement is simply nested in the else clause of the preceding if statement:

// Test measurements for tolerance.
// --------------------------------
double spec = 10.0, measured = 10.3, diff;
/* ... */
diff = measured - spec;

if ( diff >= 0.0 && diff < 0.5 )
   printf( "Upward deviation: %.2f\n", diff );
else if ( diff < 0.0 && diff > -0.5 )
   printf( "Downward deviation: %.2f\n", diff );
else
   printf( "Deviation out of tolerance!\n" );

The if conditions are evaluated one after another. As soon as one of these expression yields true, the corresponding statement is executed. Because the rest of the else if chain is cascaded under the corresponding else clause, it is alternative to the statement executed and hence skipped over. If none of the if conditions is true, then the last if statement’s else clause is executed, if present.

switch Statements

A switch statement causes the flow of program execution to jump to one of several statements according to the value of an integer expression:

switch (expression ) statement

expression has an integer type, and statement is the switch body, which contains case labels and at most one default label. The expression is evaluated once and compared with constant expressions in the case labels. If the value of the expression matches one of the case constants, the program flow jumps to the statement following that case label. If none of the case constants match, the program continues at the default label, if there is one.

Example 6-6 uses a switch statement to process the user’s selection from a menu.

Example 6-6. A switch statement
// Handle a command that the user selects from a menu.
// ---------------------------------------------------
// Declare other functions used:
int menu( void );              // Prints the menu and returns
                               // a character that the user types.
void action1( void ),
     action2( void );
/* ... */

switch ( menu() )              // Jump depending on the result of menu().
{
   case 'a':
   case 'A':  action1();       // Carry out action 1.
              break;           // Don't do any other "actions."

   case 'b':
   case 'B':  action2();       // Carry out action 2.
              break;           // Don't do the default "action."

   default:   putchar( '\a' ); // If no recognized command,
}                              // output an alert.

The syntax of the case and default labels is as follows:

case constant: statement
default:       statement

constant is a constant expression with an integer type. Each case constant in a given switch statement must have a unique value. Any of the alternative statements may be indicated by more than one case label, though.

The default label is optional, and can be placed at any position in the switch body. If there is no default label, and the control expression of the switch statement does not match any of the case constants, then none of the statements in the body of the switch statement are executed. In this case, the program flow continues with the statement following the switch body.

The switch body is usually a block statement that begins with a case label. A statement placed before the first case label in the block would never be executed.

Labels in C merely identify potential destinations for jumps in the program flow. By themselves, they have no effect on the program. Thus, after the jump from the switch to the first matching case label, program execution continues sequentially, regardless of other labels. If the statements following subsequent case labels are to be skipped over, then the last statement to be executed must be followed by a break statement. The program flow then jumps to the end of the switch body.

If variables are declared within a switch statement, they should be enclosed in a nested block:

switch ( x )
{
   case C1: {  int temp = 10;     // Declare temp only for this "case"
               /* ... */
            }
            break;
   case C2:
            /* ... */
}

Integer promotion is applied to the switch expression. The case constants are then converted to match the resulting type of the switch expression.

You can always program a selection among alternative statements using an else if chain. If the selection depends on the value of one integer expression, however, then you can use a switch statement—and should, because it makes code more readable.

Unconditional Jumps

Jump statements interrupt the sequential execution of statements, so that execution continues at a different point in the program. A jump destroys automatic variables if the jump destination is outside their scope. There are four statements that cause unconditional jumps in C: break, continue, goto, and return.

The break Statement

The break statement can occur only in the body of a loop or a switch statement, and causes a jump to the first statement after the loop or switch statement in which it is immediately contained:

break;

Thus, the break statement can be used to end the execution of a loop statement at any position in the loop body. For example, the while loop in Example 6-7 may be ended either at the user’s request (by entering a non-numeric string), or by a numeric value outside the range that the programmer wants to accept.

Example 6-7. The break statement
// Read user input of scores from 0 to 100
// and store them in an array.
// Return value: the number of values stored.
// ------------------------------------------
int getScores( short scores[ ], int len )
{
   int i = 0;
   puts( "Please enter scores between 0 and 100.\n"
         "Press <Q> and <Return> to quit.\n" );
   while ( i < len )
   {
      printf( "Score No. %2d: ", i+1 );
      if ( scanf( "%hd", &scores[i] ) != 1 )
         break;          // No number read: end the loop.
      if ( scores[i] < 0  ||  scores[i] > 100 )
      {
         printf( "%d: Value out of range.\n", scores[i] );
         break;          // Discard this value and end the loop.
      }
      ++i;
   }
   return i;             // The number of values stored.
}

The continue Statement

The continue statement can be used only within the body of a loop, and causes the program flow to skip over the rest of the current iteration of the loop:

continue;

In a while or do…while loop, the program jumps to the next evaluation of the loop’s controlling expression. In a for loop, the program jumps to the next evaluation of the third expression in the for statement, containing the operations that are performed after every loop iteration.

In Example 6-7, the second break statement terminates the data input loop as soon as an input value is outside the permissible range. To give the user another chance to enter a correct value, replace the second break with continue. Then the program jumps to the next iteration of the while loop, skipping over the statement that increments i:

// Read in scores.
// --------------------------
int getScores( short scores[ ], int len )
{
   /* ... (as in Example 6-7) ... */
   while ( i < len )
   {
      /* ... (as in Example 6-7) ... */
      if ( scores[i] < 0  ||  scores[i] > 100 )
      {
         printf( "%d : Value out of range.\n", scores[i] );
         continue;         // Discard this value and read in another.
      }
      ++i;                 // Increment the number of values stored.
   }
   return i;               // The number of values stored.
}

The goto Statement

The goto statement causes an unconditional jump to another statement in the same function. The destination of the jump is specified by the name of a label:

goto label_name;

A label is a name followed by a colon:

label_name: statement

Labels have a name space of their own, which means they can have the same names as variables or types without causing conflicts. Labels may be placed before any statement, and a statement can have several labels. Labels serve only as destinations of goto statements, and have no effect at all if the labeled statement is reached in the normal course of sequential execution. The following function uses a label after a return statement to mark the entry point to an error handling routine:

// Handle errors within the function.
// ----------------------------------
#include <stdbool.h>        // Defines bool, true
                            // and false (C99).
#define MAX_ARR_LENGTH  1000
bool calculate( double arr[ ], int len, double* result )
{
   bool error = false;
   if ( len < 1  ||  len > MAX_ARR_LENGTH )
     goto error_exit;
   for ( int i = 0; i < len; ++i )
   {
     /* ... Some calculation that could result in
      * the error flag being set ...
      */
     if ( error )
        goto error_exit;
     /* ... Calculation continues; result is
      * assigned to the variable *result ...
      */
   }
   return true;              // Flow arrives here if no error

   error_exit:               // The error handler
   *result = 0.0;
   return false;
}

You should never use a goto statement to jump into a block from outside it if the jump skips over declarations or statements that initialize variables. However, such a jump is illegal only if it leads into the scope of an array with variable length, skipping over the definition of the array (for more information about variable-length arrays, which were introduced with C99, see Chapter 8):

static const int maxSize = 1000;
double func( int n )
{
   double x = 0.0;
   if ( n > 0  &&  n < maxSize )
   {
      double arr[n];         // A variable-length array
      again:
      /* ... */
      if ( x == 0.0 )
        goto again;          // OK: the jump is entirely
   }                         // *within* the scope of arr.
   if ( x < 0.0 )
      goto again;            // Illegal: the jump leads
                             // *into* the scope of arr!
   return x;
}

Because code that makes heavy use of goto statements is hard to read, you should use them only when they offer a clear benefit, such as a quick exit from deeply nested loops. Any C program that uses goto statements can also be written without them!

Tip

The goto statement permits only local jumps; that is, jumps within a function. C also provides a feature to program non-local jumps to any point in the program, using the standard macro setjmp() and the standard function longjmp(). The macro setjmp() marks a location in the program by storing the necessary process information so that execution can be resumed at that point at another time by a call to the function longjmp(). For more information on these functions, see Part II.

The return Statement

The return statement ends execution of the current function and jumps back to where the function was called:

return [expression];

expression is evaluated and the result is given to the caller as the value of the function call. This return value is converted to the function’s return type, if necessary.

A function can contain any number of return statements:

// Return the smaller of two integer arguments
int min( int a, int b )
{
   if   ( a < b ) return a;
   else           return b;
}

The contents of this function block can also be expressed by the following single statement:

return ( a < b ? a : b );

The parentheses do not affect the behavior of the return statement. However, complex return expressions are often enclosed in parentheses for the sake of readability.

A return statement with no expression can only be used in a function of type void. In fact, such functions do not need to have a return statement at all. If no return statement is encountered in a function, the program flow returns to the caller when the end of the function block is reached. Function calls are described in more detail in Chapter 7.