3. Control Statements: Part I


Objectives

In this chapter you’ll:

Image Use the if selection statement and the if...else selection statement to select or skip actions.

Image Use the while repetition statement to execute statements in a program repeatedly.

Image Use counter-controlled repetition and sentinel-controlled repetition.

Image Use structured programming techniques.

Image Use the increment, decrement and assignment operators.


3.1. Introduction

The next two chapters discuss techniques that facilitate the development of structured computer programs.

3.2. Control Structures

Normally, statements in a program are executed one after the other in the order in which they’re written. This is called sequential execution. Various C statements we’ll soon discuss enable you to specify that the next statement to be executed may be other than the next one in sequence. This is called transfer of control.

During the 1960s, it became clear that the indiscriminate use of transfers of control was the root of a great deal of difficulty experienced by software development groups. The finger of blame was pointed at the goto statement that allows you to specify a transfer of control to one of many possible destinations in a program. The notion of so-called structured programming became almost synonymous with “goto elimination.”

The research of Bohm and Jacopini1 had demonstrated that programs could be written without any goto statements. The challenge of the era was for programmers to shift their styles to “goto-less programming.” It was not until well into the 1970s that the programming profession started taking structured programming seriously. The results were impressive, as software development groups reported reduced development times, more frequent on-time delivery of systems and more frequent within-budget completion of software projects. Programs produced with structured techniques were clearer, easier to debug and modify and more likely to be bug free in the first place.

1. C. Bohm and G. Jacopini, “Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules,” Communications of the ACM, Vol. 9, No. 5, May 1966, pp. 336–371.

Bohm and Jacopini’s work demonstrated that all programs could be written in terms of only three control structures, namely the sequence structure, the selection structure and the repetition structure. The sequence structure is simple—unless directed otherwise, the computer executes C statements one after the other in the order in which they’re written.

Flowcharts

A flowchart is a graphical representation of an algorithm or of a portion of an algorithm. Flowcharts are drawn using certain special-purpose symbols such as rectangles, diamonds, rounded rectaingles, and small circles; these symbols are connected by arrows called flowlines.

The flowchart segment of Fig. 3.1 illustrates C’s sequence structure. We use the rectangle symbol, also called the action symbol, to indicate any type of action including a calculation or an input/output operation. The flowlines in the figure indicate the order in which the actions are performed—first, grade is added to total, then 1 is added to counter. C allows us to have as many actions as we want in a sequence structure. As we’ll soon see, anywhere a single action may be placed, we may place several actions in sequence.

Image

Fig. 3.1 Flowcharting C’s sequence structure.

When drawing a flowchart that represents a complete algorithm, a rounded rectangle symbol containing the word “Begin” is the first symbol used in the flowchart; a rounded rectangle symbol containing the word “End” is the last symbol used. When drawing only a portion of an algorithm as in Fig. 3.1, the rounded rectangle symbols are omitted in favor of using small circle symbols, also called connector symbols.

Perhaps the most important flowcharting symbol is the diamond symbol, also called the decision symbol, which indicates that a decision is to be made. We’ll discuss the diamond symbol in the next section.

Selection Statements in C

C provides three types of selection structures in the form of statements. The if selection statement (Section 3.3) either selects (performs) an action if a condition is true or skips the action if the condition is false. The if...else selection statement (Section 3.4) performs an action if a condition is true and performs a different action if the condition is false. The switch selection statement (discussed in Chapter 4) performs one of many different actions, depending on the value of an expression. The if statement is called a single-selection statement because it selects or ignores a single action. The if...else statement is called a double-selection statement because it selects between two different actions. The switch statement is called a multiple-selection statement because it selects among many different actions.

Repetition Statements in C

C provides three types of repetition structures in the form of statements, namely the while statement, the do...while statement and the for statement.

That’s all there is. C has only seven control statements: sequence, three types of selection and three types of repetition. Each C program is formed by combining as many of each type of control statement as is appropriate for the algorithm the program implements. As with the sequence structure of Fig. 3.1, we’ll see that the flowchart representation of each control statement has two small circle symbols, one at the entry point to the control statement and one at the exit point. These single-entry/single-exit control statements make it easy to build clear programs. The control-statement flowchart segments can be attached to one another by connecting the exit point of one control statement to the entry point of the next. We call this control-statement stacking. There’s only one other way control statements may be connected—a method called control-statement nesting. Thus, any C program we’ll ever need to build can be constructed from only seven different types of control statements combined in only two ways. This is the essence of simplicity.

3.3. The if Selection Statement

Selection statements are used to choose among alternative courses of action. For example, suppose the passing grade on an exam is 60. The C statement

if ( grade >= 60 ) {
   puts( "Passed" );
} // end if

determines whether the condition grade >= 60 is true or false. If the condition is true, then "Passed" is printed, and the next statement in order is performed. If the condition is false, the printing is skipped, and the next statement in order is performed. The second line of this selection structure is indented. Such indentation is optional, but it’s highly recommended, as it helps emphasize the inherent structure of structured programs. The C compiler ignores white-space characters such as blanks, tabs and newlines used for indentation and vertical spacing.

The flowchart of Fig. 3.2 illustrates the single-selection if statement. The diamond-shaped decision symbol contains an expression, such as a condition, that can be either true or false. The decision symbol has two flowlines emerging from it. One indicates the direction to take when the expression in the symbol is true and the other the direction to take when the expression is false. Decisions can be based on conditions containing relational or equality operators. In fact, a decision can be based on any expression—if the expression evaluates to zero, it’s treated as false, and if it evaluates to nonzero, it’s treated as true.

Image

Fig. 3.2 Flowcharting the single-selection if statement.

3.4. The if...else Selection Statement

The if selection statement performs an indicated action only when the condition is true; otherwise the action is skipped. The if...else selection statement allows you to specify that different actions are to be performed when the condition is true and when it’s false. For example, the statement

if ( grade >= 60 ) {
   puts( "Passed" );
} // end if
else {
   puts( "Failed" );
} // end else

prints "Passed" if the student’s grade is greater than or equal to 60 and "Failed" if the student’s grade is less than 60. In either case, after printing occurs, the next statement in sequence is performed. The body of the else is also indented. The flowchart of Fig. 3.3 illustrates the flow of control in this if...else statement.

Image

Fig. 3.3 Flowcharting the double-selection if...else statement.

Conditional Operators and Conditional Expressions

C provides the conditional operator (?:), which is closely related to the if...else statement. The conditional operator is C’s only ternary operator—it takes three operands. These together with the conditional operator form a conditional expression. The first operand is a condition. The second operand is the value for the entire conditional expression if the condition is true and the third operand is the value for the entire conditional expression if the condition is false. For example, the puts statement

puts( grade >= 60 ? "Passed" : "Failed" );

contains as its argument a conditional expression that evaluates to the string "Passed" if the condition grade >= 60 is true and to the string "Failed" if the condition is false. The puts statement performs in essentially the same way as the preceding if...else statement.

The second and third operands in a conditional expression can also be actions to be executed. For example, the conditional expression

grade >= 60 ? puts( "Passed" ) : puts( "Failed" );

is read, “If grade is greater than or equal to 60, then puts("Passed"), otherwise puts("Failed").” This, too, is comparable to the preceding if...else statement. We’ll see that conditional operators can be used in some places where if...else statements cannot.

Nested if...else Statements

Nested if...else statements test for multiple cases by placing if...else statements inside if...else statements. For example, the following code segment will print A for exam grades greater than or equal to 90, B for grades greater than or equal to 80 (but less than 90), C for grades greater than or equal to 70 (but less than 80), D for grades greater than or equal to 60 (but less than 70), and F for all other grades.

if ( grade >= 90 ) {
   puts( "A" );
} // end if
else {
   if ( grade >= 80 ) {
      puts("B");
   } // end if
   else {
      if ( grade >= 70 ) {
         puts("C");
      } // end if
      else {
         if ( grade >= 60 ) {
            puts( "D" );
         } // end if
         else {
            puts( "F" );
         } // end else
      } // end else
   } // end else
} // end else

If the variable grade is greater than or equal to 90, all four conditions will be true, but only the puts statement after the first test will by executed. After that, the else part of the outer if...else statement is skipped and execution proceeds with the first statement after the entire code segment.

You may prefer to write the preceding if statement as

if ( grade >= 90 ) {
   puts( "A" );
} // end if
else if ( grade >= 80 ) {
   puts( "B" );
} // end else if
else if ( grade >= 70 ) {
   puts( "C" );
} // end else if
else if ( grade >= 60 ) {
   puts( "D" );
} // end else if
else {
   puts( "F" );
} // end else

As far as the C compiler is concerned, both forms are equivalent. The latter is popular because it avoids the deep indentation of the code to the right. Such indentation can leave little room on a line, forcing lines to be split and decreasing program readability.

The if selection statement expects only one statement in its body—if you have only one statement in the if’s body, you do not need to enclose it in braces. To include several statements in the body of an if, you must enclose the set of statements in braces ({ and }). A set of statements contained within a pair of braces is called a compound statement or a block.


Image Software Engineering Observation 3.1

A compound statement can be placed anywhere in a program that a single statement can be placed.


The following example includes a compound statement in the else part of an if...else statement.

if ( grade >= 60 ) {
   puts( "Passed." );
} // end if
else {
   puts( "Failed." );
   puts( "You must take this course again." );
} // end else

In this case, if grade is less than 60, the program executes both puts statements in the body of the else and prints

Failed.
You must take this course again.

The braces surrounding the two statements in the else clause are important. Without them, the statement

puts( "You must take this course again." );

would be outside the body of the else part of the if and would always execute, regardless of whether grade was less than 60.

Just as a compound statement can be placed anywhere a single statement can be placed, it’s also possible to have no statement at all, i.e., the empty statement. The empty statement is represented by placing a semicolon (;) where a statement would normally be.


Image Common Programming Error 3.1

Placing a semicolon after the condition in an if statement as in if ( grade >= 60 ); leads to a logic error in single-selection if statements and a syntax error in double-selection if statements.


3.5. The while Repetition Statement

A repetition statement (also called an iteration statement) allows you to specify that an action is to be repeated while some condition remains true. The action will be performed repeatedly while the condition remains true. Eventually, the condition will become false. At this point, the repetition terminates, and the first statement after the repetition statement is executed.

As an example of a while repetition statement, consider a program segment designed to find the first power of 3 larger than 100. Suppose the integer variable product has been initialized to 3. When the following while repetition statement finishes executing, product will contain the desired answer:

product = 3;
while ( product <= 100 ) {
   product = 3 * product;
} // end while

while Repetition Statement Body

The statement(s) contained in the while repetition statement constitute the body of the while. The while statement body may be a single statement or a compound statement enclosed in braces ({ and }).


Image Common Programming Error 3.2

Not providing in the body of a while statement an action that eventually causes the condition in the while to become false normally causes an infinite loop.


Flowcharting the while Repetition Statement

The flowchart of Fig. 3.4 illustrates the flow of control in the while repetition statement. The flowchart clearly shows the repetition. The flowline emerging from the rectangle wraps back to the decision, which is tested each time through the loop until the decision eventually becomes false. At this point, the while statement is exited and control passes to the next statement in the program.

Image

Fig. 3.4 Flowcharting the while repetition statement.

When the while statement is entered, the value of product is 3. The variable product is repeatedly multiplied by 3, taking on the values 9, 27 and 81 successively. When product becomes 243, the condition in the while statement, product <= 100, becomes false. This terminates the repetition, and the final value of product is 243. Program execution continues with the next statement after the while.

3.6. Class Average with Counter-Controlled Repetition

Next, we’ll solve several variations of a class-averaging problem. Consider the following problem statement:

A class of ten students took a quiz. The grades (integers in the range 0 to 100) for this quiz are available to you. Determine the class average on the quiz.

The class average is equal to the sum of the grades divided by the number of students. The C program that solves this problem inputs each of the grades, performs the averaging calculation and prints the result (Fig. 3.5).


 1   // Fig. 3.5: fig03_05.c
 2   // Class average program with counter-controlled repetition.
 3   #include <stdio.h>
 4
 5   // function main begins program execution
 6   int main( void )
 7   {
 8      unsigned int counter; // number of grade to be entered next
 9      int grade; // grade value
10      int total; // sum of grades entered by user
11      int average; // average of grades
12
13      // initialization phase
14      total = 0; // initialize total
15      counter = 1; // initialize loop counter
16
17      // processing phase
18      while ( counter <= 10 ) { // loop 10 times
19         printf( "%s", "Enter grade: " ); // prompt for input
20         scanf( "%d", &grade ); // read grade from user
21         total = total + grade; // add grade to total
22         counter = counter + 1; // increment counter
23      } // end while
24
25      // termination phase
26      average = total / 10; // integer division
27
28      printf( "Class average is %d ", average ); // display result
29   } // end function main


Enter grade: 98
Enter grade: 76
Enter grade: 71
Enter grade: 87
Enter grade: 83
Enter grade: 90
Enter grade: 57
Enter grade: 79
Enter grade: 82
Enter grade: 94
Class average is 81


Fig. 3.5 Class-average problem with counter-controlled repetition.

We use counter-controlled repetition to input the grades one at a time. This technique uses a counter to specify the number of times a set of statements should execute. In this example, repetition terminates when the counter exceeds 10.

We use a total and a counter. Because the counter variable is used to count from 1 to 10 in this program (all positive values), we declared the variable as an unsigned int, which can store only non-negative values (that is, 0 and higher).

The averaging calculation in the program produced an integer result of 81. Actually, the sum of the grades in this sample execution is 817, which when divided by 10 should yield 81.7, i.e., a number with a decimal point. We’ll see how to deal with such floating-point numbers in the next section.

3.7. Class Average with Sentinel-Controlled Repetition

Let’s generalize the class-average problem. Consider the following problem:

Develop a class-averaging program that will process an arbitrary number of grades each time the program is run.

In the first class-average example, the number of grades (10) was known in advance. In this example, no indication is given of how many grades are to be entered. The program must process an arbitrary number of grades.

One way to solve this problem is to use a special value called a sentinel value (also called a signal value, a dummy value, or a flag value) to indicate “end of data entry.” The user types in grades until all legitimate grades have been entered. The user then types the sentinel value to indicate that “the last grade has been entered.”

Clearly, the sentinel value must be chosen so that it cannot be confused with an acceptable input value. Because grades on a quiz are normally nonnegative integers, –1 is an acceptable sentinel for this problem. Thus, a run of the class-average program might process a stream of inputs such as 95, 96, 75, 74, 89 and –1. The program would then compute and print the class average for the grades 95, 96, 75, 74, and 89 (the sentinel value –1 should not enter into the averaging calculation).

The program and a sample execution are shown in Fig. 3.6. Although only integer grades are entered, the averaging calculation is likely to produce a number with a decimal point. The type int cannot represent such a number. The program introduces the data type float to handle such floating-point numbers.


 1   // Fig. 3.6: fig03_06.c
 2   // Class-average program with sentinel-controlled repetition.
 3   #include <stdio.h>
 4
 5   // function main begins program execution
 6   int main( void )
 7   {
 8      unsigned int counter; // number of grades entered
 9      int grade; // grade value
10      int total; // sum of grades
11
12      float average; // number with decimal point for average
13
14      // initialization phase
15      total = 0; // initialize total
16      counter = 0; // initialize loop counter
17
18      // processing phase
19      // get first grade from user
20      printf( "%s", "Enter grade, -1 to end: " ); // prompt for input
21      scanf( "%d", &grade ); // read grade from user                 
22
23      // loop while sentinel value not yet read from user
24      while ( grade != -1 ) {
25         total = total + grade; // add grade to total
26         counter = counter + 1; // increment counter
27
28         // get next grade from user
29         printf( "%s", "Enter grade, -1 to end: " ); // prompt for input
30         scanf("%d", &grade); // read next grade                        
31      } // end while
32
33      // termination phase
34      // if user entered at least one grade
35      if ( counter != 0 ) {
36
37         // calculate average of all grades entered
38         average = ( float ) total / counter; // avoid truncation
39
40         // display average with two digits of precision
41         printf( "Class average is %.2f ", average );
42      } // end if
43      else { // if no grades were entered, output message
44         puts( "No grades were entered" );
45      } // end else
46   } // end function main


Enter grade, -1 to end: 75
Enter grade, -1 to end: 94
Enter grade, -1 to end: 97
Enter grade, -1 to end: 88
Enter grade, -1 to end: 70
Enter grade, -1 to end: 64
Enter grade, -1 to end: 83
Enter grade, -1 to end: 89
Enter grade, -1 to end: -1
Class average is 82.50



Enter grade, -1 to end: -1
No grades were entered


Fig. 3.6 Class-average program with sentinel-controlled repetition.

Notice the compound statement in the while loop (line 24) in Fig. 3.6. Once again, the braces are necessary to ensure that all four statements are executed within the loop. Without the braces, the last three statements in the body of the loop would fall outside the loop, causing the computer to interpret this code incorrectly as follows.

while ( grade != -1 )
   total = total + grade; // add grade to total
counter = counter + 1; // increment counter
printf( "%s", "Enter grade, -1 to end: " ); // prompt for input
scanf( "%d", &grade ); // read next grade

This would cause an infinite loop if the user did not input -1 for the first grade.


Image Good Programming Practice 3.1

In a sentinel-controlled loop, the prompts requesting data entry should explicitly remind the user what the sentinel value is.


Converting Between Types Explicitly and Implicitly

Averages do not always evaluate to integer values. Often, an average is a floating point value such as 7.2 or –93.5 that can be represented by the data type float. The variable average is defined to be of type float (line 12) to capture the fractional result of our calculation. However, the result of the calculation total / counter is an integer because total and counter are both integer variables. Dividing two integers results in integer division in which any fractional part of the calculation is truncated. Because the calculation is performed before the result is assigned to average, the fractional part is lost. To produce a floating-point calculation with integer values, we must create temporary values that are floating-point numbers. C provides the unary cast operator to accomplish this task. Line 38

average = ( float ) total / counter;

includes the cast operator (float), which creates a temporary floating-point copy of its operand, total. The value in total is still an integer. The cast operator performs an explicit conversion. The calculation now consists of a floating-point value (the temporary float version of total) divided by the unsigned int value stored in counter. C evaluates arithmetic expressions only in which the data types of the operands are identical. To ensure that the operands are of the same type, the compiler performs an operation called implicit conversion on selected operands. For example, in an expression containing the data types unsigned int and float, copies of unsigned int operands are made and converted to float. In our example, after a copy of counter is made and converted to float, the calculation is performed and the result of the floating-point division is assigned to average. C provides a set of rules for conversion of operands of different types. We discuss this further in Chapter 5.

Cast operators are available for most data types—they’re formed by placing parentheses around a type name. Each cast operator is a unary operator, i.e., an operator that takes only one operand. In Chapter 2, we studied the binary arithmetic operators. C also supports unary versions of the plus (+) and minus (-) operators, so you can write expressions such as +7 or –5. Cast operators associate from right to left and have the same precedence as other unary operators such as unary + and unary -. This precedence is one level higher than that of the multiplicative operators *, / and %.

Formatting Floating-Point Numbers

Figure 3.6 uses the printf conversion specifier %.2f (line 41) to print the value of average. The f specifies that a floating-point value will be printed. The .2 is the precision with which the value will be displayed—with 2 digits to the right of the decimal point. If the %f conversion specifier is used (without specifying the precision), the default precision of 6 is used—exactly as if the conversion specifier %.6f had been used. When floating-point values are printed with precision, the printed value is rounded to the indicated number of decimal positions. The value in memory is unaltered. When the following statements are executed, the values 3.45 and 3.4 are printed.

printf( "%.2f ", 3.446 ); // prints 3.45
printf( "%.1f ", 3.446 ); // prints 3.4


Image Common Programming Error 3.3

Using precision in a conversion specification in the format control string of a scanf statement is incorrect. Precisions are used only in printf conversion specifications.


Notes on Floating-Point Numbers

Although floating-point numbers are not always “100% precise,” they have numerous applications. For example, when we speak of a “normal” body temperature of 98.6, we do not need to be precise to a large number of digits. When we view the temperature on a thermometer and read it as 98.6, it may actually be 98.5999473210643. The point here is that calling this number simply 98.6 is fine for most applications. We’ll say more about this issue later.

Another way floating-point numbers develop is through division. When we divide 10 by 3, the result is 3.3333333... with the sequence of 3s repeating infinitely. The computer allocates only a fixed amount of space to hold such a value, so the stored floating-point value can be only an approximation.


Image Common Programming Error 3.4

Using floating-point numbers in a manner that assumes they’re represented precisely can lead to incorrect results. Floating-point numbers are represented only approximately by most computers.



Image Error-Prevention Tip 3.1

Do not compare floating-point values for equality.


3.8. Nested Control Statements

We’ve seen that control statements may be stacked on top of one another (in sequence). In this case study we’ll see the only other structured way control statements may be connected in C, namely through nesting of one control statement within another.

Consider the following problem statement:

A college offers a course that prepares students for the state licensing exam for real estate brokers. Last year, 10 of the students who completed this course took the licensing examination. Naturally, the college wants to know how well its students did on the exam. You’ve been asked to write a program to summarize the results. You’ve been given a list of these 10 students. Next to each name a 1 is written if the student passed the exam and a 2 if the student failed.

Your program should analyze the results of the exam as follows:

1. Input each test result (i.e., a 1 or a 2). Display the prompting message “Enter result” each time the program requests another test result.

2. Count the number of test results of each type.

3. Display a summary of the test results indicating the number of students who passed and the number who failed.

4. If more than eight students passed the exam, print the message “Bonus to instructor!”

After reading the problem statement carefully, we make the following observations:

1. The program must process 10 test results. A counter-controlled loop will be used.

2. Each test result is a number—either a 1 or a 2. Each time the program reads a test result, the program must determine whether the number is a 1 or a 2. We test for a 1 in our algorithm. If the number is not a 1, we assume that it’s a 2. As an exercise, you should modify the program to explicitly test for 2 and issue an error message if the test result is neither 1 nor 2.

3. Two counters are used—one to count the number of students who passed the exam and one to count the number of students who failed the exam.

4. After the program has processed all the results, it must decide whether more than 8 students passed the exam.

The C program and two sample executions are shown in Fig. 3.7. We’ve taken advantage of a feature of C that allows initialization to be incorporated into definitions (lines 9–11). Such initialization occurs at compile time. Also, notice that when you output an unsigned int you use the %u conversion specifier (lines 33–34).


 1   // Fig. 3.7: fig03_07.c
 2   // Analysis of examination results.
 3   #include <stdio.h>
 4
 5   // function main begins program execution
 6   int main( void )
 7   {
 8      // initialize variables in definitions
 9      unsigned int passes = 0; // number of passes    
10      unsigned int failures = 0; // number of failures
11      unsigned int student = 1; // student counter    
12      int result; // one exam result
13
14      // process 10 students using counter-controlled loop
15      while ( student <= 10 ) {
16
17         // prompt user for input and obtain value from user
18         printf( "%s", "Enter result ( 1=pass,2=fail ): " );
19         scanf( "%d", &result );
20
21         // if result 1, increment passes
22         if ( result == 1 ) {
23            passes = passes + 1;
24         } // end if
25         else { // otherwise, increment failures
26            failures = failures + 1;
27         } // end else
28
29         student = student + 1; // increment student counter
30      } // end while
31
32      // termination phase; display number of passes and failures
33      printf( "Passed %u ", passes );
34      printf( "Failed %u ", failures );
35
36      // if more than eight students passed, print "Bonus to instructor!"
37      if ( passes > 8 ) {
38         puts( "Bonus to instructor!" );
39      } // end if
40   } // end function main


Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 2
Enter Result (1=pass,2=fail): 2
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 2
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 2
Passed 6
Failed 4



Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 2
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Enter Result (1=pass,2=fail): 1
Passed 9
Failed 1
Bonus to instructor!


Fig. 3.7 Analysis of examination results.

3.9. Assignment Operators

C provides several assignment operators for abbreviating assignment expressions. For example, the statement

c = c + 3;

can be abbreviated with the addition assignment operator += as

c += 3;

The += operator adds the value of the expression on the right of the operator to the value of the variable on the left of the operator and stores the result in the variable on the left of the operator. Any statement of the form

variable = variable operator expression;

where operator is one of the binary operators +, -, *, / or % (or others we’ll discuss in Chapter 10), can be written in the form

variable operator=  expression;

Thus the assignment c += 3 adds 3 to c. Figure 3.8 shows the arithmetic assignment operators, sample expressions using these operators and explanations.

Image

Fig. 3.8 Arithmetic assignment operators.

3.10. Increment and Decrement Operators

C also provides the unary increment operator, ++, and the unary decrement operator, --, which are summarized in Fig. 3.9. If a variable c is to be incremented by 1, the increment operator ++ can be used rather than the expressions c = c + 1 or c += 1. If increment or decrement operators are placed before a variable (i.e., prefixed), they’re referred to as the preincrement or predecrement operators, respectively. If increment or decrement operators are placed after a variable (i.e., postfixed), they’re referred to as the postincrement or postdecrement operators, respectively. Preincrementing (predecrementing) a variable causes the variable to be incremented (decremented) by 1, then its new value is used in the expression in which it appears. Postincrementing (postdecrementing) the variable causes the current value of the variable to be used in the expression in which it appears, then the variable value is incremented (decremented) by 1.

Image

Fig. 3.9 Increment and decrement operators

Differences Between Preincrement and Postincrement

Figure 3.10 demonstrates the difference between the preincrementing and the postincrementing versions of the ++ operator. Postincrementing the variable c causes it to be incremented after it’s used in the printf statement. Preincrementing the variable c causes it to be incremented before it’s used in the printf statement.


 1   // Fig. 3.10: fig03_10.c
 2   // Preincrementing and postincrementing.
 3   #include <stdio.h>
 4
 5   // function main begins program execution
 6   int main( void )
 7   {
 8      int c; // define variable
 9
10      // demonstrate postincrement
11      c = 5; // assign 5 to c
12      printf( "%d ", c ); // print 5
13      printf( "%d ", c++ ); // print 5 then postincrement
14      printf( "%d ", c ); // print 6                   
15
16      // demonstrate preincrement
17      c = 5; // assign 5 to c
18      printf( "%d ", c ); // print 5
19      printf( "%d ", ++c ); // preincrement then print 6
20      printf( "%d ", c ); // print 6                    
21   } // end function main


5
5
6

5
6
6


Fig. 3.10 Preincrementing and postincrementing.

The program displays the value of c before and after the ++ operator is used. The decrement operator (--) works similarly.


Image Good Programming Practice 3.2

Unary operators should be placed directly next to their operands with no intervening spaces.


The three assignment statements in Fig. 3.7

passes = passes + 1;
failures = failures + 1;
student = student + 1;

can be written more concisely with assignment operators as

passes += 1;
failures += 1;
student += 1;

with preincrement operators as

++passes;
++failures;
++student;

or with postincrement operators as

passes++;
failures++;
student++;

It’s important to note here that when incrementing or decrementing a variable in a statement by itself, the preincrement and postincrement forms have the same effect. It’s only when a variable appears in the context of a larger expression that preincrementing and postincrementing have different effects (and similarly for predecrementing and post-decrementing). Of the expressions we’ve studied thus far, only a simple variable name may be used as the operand of an increment or decrement operator.


Image Common Programming Error 3.5

Attempting to use the increment or decrement operator on an expression other than a simple variable name is a syntax error, e.g., writing ++(x + 1).



Image Error-Prevention Tip 3.2

C generally does not specify the order in which an operator’s operands will be evaluated (although we’ll see exceptions to this for a few operators in Chapter 4). Therefore you should use increment or decrement operators only in statements in which one variable is incremented or decremented by itself.


Figure 3.11 lists the precedence and associativity of the operators introduced to this point. The operators are shown top to bottom in decreasing order of precedence. The second column indicates the associativity of the operators at each level of precedence. Notice that the unary operators increment (++), decrement (--), plus (+), minus (-) and casts, and the assignment operators =, +=, -=, *=, /= and %=, and the conditional operator (?:) associate from right to left. The third column names the various groups of operators. All other operators in Fig. 3.11 associate from left to right.

Image

Fig. 3.11 Precedence and associativity of the operators encountered so far in the text.

3.11. Secure C Programming

Arithmetic Overflow

Figure 2.5 presented an addition program which calculated the sum of two int values (line 18) with the statement

sum = integer1 + integer2; // assign total to sum

Even this simple statement has a potential problem—adding the integers could result in a value that’s too large to store in an int variable. This is known as arithmetic overflow and can cause undefined behavior, possibly leaving a system open to attack.

The maximum and minimum values that can be stored in an int variable are represented by the constants INT_MAX and INT_MIN, respectively, which are defined in the header <limits.h>. There are similar constants for the other integral types that we’ll be introducing in Chapter 4. You can see your platform’s values for these constants by opening the header <limits.h> in a text editor.

It’s considered a good practice to ensure that before you perform arithmetic calculations like the one in line 18 of Fig. 2.5, they will not overflow. The code for doing this is shown on the CERT website www.securecoding.cert.org—just search for guideline “INT32-C.” The code uses the && (logical AND) and || (logical OR) operators (introduced in Chapter 4). In industrial-strength code, you should perform checks like these for all calculations. In later chapters, we’ll show other programming techniques for handling such errors.

Avoid Division-by-Zero

In our class average example, it’s possible that the denominator could be zero. In industrial-strength applications you should not allow that to occur.


Image Error-Prevention Tip 3.3

When performing division by an expression whose value could be zero, explicitly test for this case and handle it appropriately in your program (such as printing an error message) rather than allowing the fatal error to occur.


Unsigned Integers

In Fig. 3.5, line 8 declared as an unsigned int the variable counter because it’s used to count only non-negative values. In general, counters that should store only non-negative values should be declared with unsigned before the integer type. Variables of unsigned types can represent values from 0 to approximately twice the positive range of the corresponding signed integer types. You can determine your platform’s maximum unsigned int value with the constant UINT_MAX from <limits.h>.

The class-averaging program in Fig. 3.5 could have declared as unsigned int the variables grade, total and average. Grades are normally values from 0 to 100, so the total and average should each be greater than or equal to 0. We declared those variables as ints because we can’t control what the user actually enters—the user could enter negative values. Worse yet, the user could enter a value that’s not even a number. (We’ll show how to deal with such inputs later in the book.)

Sometimes sentinel-controlled loops use invalid values to terminate a loop. For example, the class-averaging program of Fig. 3.6 terminates the loop when the user enters the sentinel -1 (an invalid grade), so it would be improper to declare variable grade as an unsigned int. As you’ll see, the end-of-file (EOF) indicator—which is introduced in the next chapter and is often used to terminate sentinel-controlled loops—is also a negative number. For more information, see Chapter 5, Integer Security, of Robert Seacord’s book Secure Coding in C and C++.

scanf_s and printf_s

The C11 standard’s Annex K introduces more secure versions of printf and scanf called printf_s and scanf_s. Annex K is designated as optional, so not every C vendor will implement it.

Microsoft implemented its own versions of printf_s and scanf_s prior to the publication of the C11 standard and immediately began issuing compiler warnings for every scanf call. The warnings say that scanf is deprecated—it should no longer be used—and that you should consider using scanf_s instead.

Many organizations have coding standards that require code to compile without warning messages. There are two ways to eliminate Visual C++’s scanf warnings—you can use scanf_s instead of scanf or you can disable these warnings. For the input statements we’ve used so far, Visual C++ users can simply replace scanf with scanf_s. You can disable the warning messages in Visual C++ as follows:

1. Type Alt F7 to display the Property Pages dialog for your project.

2. In the left column, expand Configuration Properties > C/C++ and select Preprocessor.

3. In the right column, at the end of the value for Preprocessor Definitions, insert

;_CRT_SECURE_NO_WARNINGS

4. Click OK to save the changes.

You’ll no longer receive warnings on scanf (or any other functions that Microsoft has deprecated for similar reasons). For industrial-strength coding, disabling the warnings is discouraged. We’ll say more about how to use scanf_s and printf_s in a later Secure C Coding Guidelines section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.170.63