While You re at ItMultiple Functions

I l @ ve RuBoard

While You're at ItMultiple Functions

So far, these programs have used the standard printf() function. Listing 2.3 shows you how to incorporate a function of your own”besides main() ”into a program.

Listing 2.3 The two_func.c program.
 /* two_func.c -- a program using two functions in one file */ #include <stdio.h> void butler (void);            /* ANSI C function prototyping */                    /* pre-ANSI C uses void butler(); instead */ int main(void) {    printf(I will summon the butler function.\n);    butler();    printf(Yes. Bring me some tea and writeable CD-ROMS.\n);[sr]    return 0; } void butler(void)            /* start of function definition */ {    printf("You rang, sir?\n"); } 

The output looks like this:

 I will summon the butler function. You rang, sir? Yes. Bring me some tea and writeable CD-ROMS. 

The butler() function appears three times in this program. The first appearance is in the prototype , which informs the compiler about the functions to be used. The second appearance is in main() in the form of a function call . Finally, the program presents the function definition , which is the source code for the function itself. Let's look at each of these three appearances in turn .

Prototypes are an ANSI C addition, and pre-ANSI compilers might not recognize them. (We'll tell you in a moment what to do with pre-ANSI compilers.) A prototype is a form of declaration that tells the compiler you are using a particular function. It also specifies properties of the function. For example, the first void in the prototype for the butler() function indicates that butler() does not have a return value. (In general, a function can return a value to the calling function for its use, but butler () doesn't.) The second void , the one in butler(void) means that the butler() function has no arguments. Therefore, when the compiler reaches the point in main() where butler() is used, it can check to see whether butler() is used correctly. Note that void is used to mean "empty," not "invalid."

Pre-ANSI C supports a more limited form of function declaration in which you just specify the return type but omit describing the arguments.

 void butler(); 

Older C code uses function declarations like the preceding one instead of function prototypes. ANSI C recognizes this older form but indicates it will be phased out in time. Later chapters in this book return to prototyping, function declarations, and return values.

Next , you invoke butler() in main() simply by giving its name , including parentheses. When butler() finishes its work, the program moves to the next statement in main() .

Finally, the function butler() is defined in the same manner as main() , with a function header and the body enclosed in braces. The header repeats the information given in the prototype: butler() takes no arguments and has no return value. For pre-ANSI C, omit the second void .

One point to note is that it is the location of the butler() call in main() ”not the location of the butler() definition in the file”that determines when the butler() function is executed. You could, for instance, put the butler() definition above the main() definition in this program, and the program would still run the same, with the butler() function executed between the two calls to printf() in main() . Remember, all C programs begin execution with main() , no matter where main() is located in the program files. However, C practice is to list main() first because it normally provides the basic framework for a program.

The ANSI C standard recommends that you provide function prototypes for all functions you use. The standard include files take care of this task for the standard library functions. For example, under ANSI C, the stdio.h file has a function prototype for printf() . When we introduce defining functions with return values in Chapter 6, "C Control Statements: Looping," we'll show you how to extend prototyping to non- void functions.

Debugging

Now that you can write a simple C program, you are in a position to make simple errors. Listing 2.4 presents a program with some errors. See how many you can spot.

Listing 2.4 The nogood.c program.
 /*  nogood.c -- a program with errors */ #include <stdio.h> int main(void) (int n, int n2, int n3;      /* this program has several errors   n = 5;   n2 = n * n;   n3 = n2 * n2;   printf(n = %d, n squared = %d, n cubed = %d\n", n, n2, n3)   return 0;) 

Syntax Errors

Listing 2.4 contains several syntax errors . You commit a syntax error when you don't follow C's rules. It's analogous to a grammatical error in English. For instance, consider the following sentence : Bugs frustrate be can . This sentence uses valid English words but doesn't follow the rules for word order, and it doesn't have quite the right words, anyway. C syntax errors use valid C symbols in the wrong places.

So what syntax errors did nogood.c make? First, it uses parentheses instead of braces to mark the body of the function”it uses a valid C symbol in the wrong place. Second, the declaration should have been this:

 int n, n2, n3; 

or perhaps this:

 int n; int n2; int n3; 

Next, the example omits the */ symbol pair necessary to complete a comment. Finally, it omits the mandatory semicolon that should terminate the printf() statement.

How do you detect syntax errors? First, before compiling, you can look through the source code and see if you spot anything obvious. Second, you can examine errors found by the compiler because part of its job is to detect syntax errors. When you compile this program, the compiler reports back any errors it finds, identifying the nature and location of each error.

However, the compiler can get confused . A true syntax error in one location might cause the compiler to mistakenly think it has found other errors. For instance, because the example does not declare n2 and n3 correctly, the compiler might think it has found further errors whenever those variables are used. If some of the supposed errors don't make sense to you, first correct the errors before them, recompile, and see if the compiler still complains. Continue in this way until the program works. Another common occurrence is for the error to be reported a line late. For instance, the compiler may not deduce that a semicolon is missing until it tries to compile the next line. So if the compiler complains of a missing semicolon on a line that has one, check the line before.

Semantic Errors

Semantic errors are errors in meaning. For instance, consider the following sentence: Furry inflation thinks greenly . The syntax is fine because adjectives, nouns, verbs, and adverbs are in the right places, but the sentence doesn't mean anything. In C, you commit a semantic error when you follow the rules of C correctly but to an incorrect end. The example has one such error:

 n3 = n2 * n2; 

Here, n3 is supposed to represent the cube of n , but I've set it up to be the fourth power of n .

The compiler does not detect semantic errors, for they don't violate C rules. The compiler has no way of divining your true intentions. That leaves it to you to find these kinds of errors. One way is to compare what a program does to what you expected it to do. For instance, suppose you fix the syntax errors in the example so that it now reads as shown in Listing 2.5.

Listing 2.5 The stillbad.c program.
 /* stillbad.c -- a program with its syntax errors fixed */ #include <stdio.h> int main(void) {   int n, n2, n3;      /* this program has a semantic error */   n = 5;   n2 = n * n;   n3 = n2 * n2;   printf("n = %d, n squared = %d, n cubed = %d\n", n, n2, n3);   return 0; } 

Its output is this:

 n = 5, n squared = 25, n cubed = 625 

If you are cube-wise, you'll see that 625 is the wrong value. The next stage is to track down how you wound up with this answer. For this example, you probably can spot the error by inspection. In general, however, you need to take a more systematic approach. One method is to pretend you are the computer and to follow the program steps one by one. Let's try that method now.

The body of the program starts by declaring three variables: n , n2 , and n3 . You can simulate this situation by drawing three boxes and labeling them with the variable names (see Figure 2.6). Next, the program assigns 5 to n . Simulate that by writing 5 into the n box. Next, the program multiplies n by n and assigns the result to n2 , so look in the n box, see that the value is 5 , multiply 5 by 5 to get 25 , and place 25 in box n2 . To duplicate the next C statement ( n3 = n2 * n2; ), look in n2 and find 25 . You multiply 25 by 25 , get 625 , and place it in n3 . Aha! You are squaring n2 instead of multiplying it by n .

Figure 2.6. Tracing a program.
graphics/02fig06.jpg

Well, perhaps this procedure is overkill for this example, but going through a program step-by-step in this fashion is often the best way to see what's going on.

Program State

By tracing the program step-by-step, keeping track of each variable, you monitor the program state. The program state is simply the set of values of all the variables at a given point in program execution. It is a snapshot of the current state of computation.

We just discussed one method of tracing the state: executing the program step-by-step yourself. In a program that makes, say, 10,000 iterations, you might not feel up to that task. Still, you can go through a few iterations to see if your program does what you intend. However, there is always the possibility that you will execute the steps as you intended them to be executed instead of as you actually wrote them, so try to be faithful to the actual code.

Another approach to locating semantic problems is to sprinkle extra printf() statements throughout to monitor the values of selected variables at key points in the program. Seeing how the values change can illuminate what's happening. After you have the program working to your satisfaction, you can remove the extra statements and recompile.

A third method for examining the program states is to use a debugger. A debugger is a program that enables you to run another program step-by-step and examine the value of that program's variables. Debuggers come in various levels of ease of use and sophistication. The more advanced debuggers show which line of source code is being executed. This is particularly handy for programs with alternative paths of execution because it is easy to see which particular paths are being followed. If your compiler comes with a debugger, take time now to learn how to use it. Try it with Listing 2.4, for example.

I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net