14. Other Topics


Objectives

In this chapter you’ll:

Image Redirect keyboard input to come from a file.

Image Redirect screen output to be placed in a file.

Image Write functions that use variable-length argument lists.

Image Process command-line arguments.

Image Compile multiple-source-file programs.

Image Assign specific types to numeric constants.

Image Process external asynchronous events in a program.

Image Dynamically allocate arrays and resize memory that was dynamically allocated previously.


14.1. Introduction

This chapter presents several additional topics. Many of the capabilities discussed here are specific to particular operating systems.

14.2. Redirecting I/O

In command-line applications, normally the input is received from the keyboard (standard input), and the output is displayed on the screen (standard output). On most computer systems—Linux/UNIX, Mac OS X and Windows systems in particular—it’s possible to redirect inputs to come from a file rather than the keyboard and redirect outputs to be placed in a file rather than on the screen. Both forms of redirection can be accomplished without using the file-processing capabilities of the standard library.

There are several ways to redirect input and output from the command line—that is, a Command Prompt window in Windows, a shell in Linux or a Terminal window in Mac OS X. Consider the executable file sum (on Linux/UNIX systems) that inputs integers one at a time and keeps a running total of the values until the end-of-file indicator is set, then prints the result. Normally the user inputs integers from the keyboard and enters the end-of-file key combination to indicate that no further values will be input. With input redirection, the input can be stored in a file. For example, if the data is stored in file input, the command line

$ sum < input

executes the program sum; the redirect input symbol (<) indicates that the data in file input is to be used as input by the program. Redirecting input on a Windows system is performed identically.

The character $ is a typical Linux/UNIX command-line prompt (some systems use a % prompt or other symbol). Redirection like this is an operating system function, not a C feature.

The second method of redirecting input is piping. A pipe (|) causes the output of one program to be redirected as the input to another program. Suppose program random outputs a series of random integers; the output of random can be “piped” directly to program sum using the command line

$ random | sum

This causes the sum of the integers produced by random to be calculated. Piping is performed identically in Linux/UNIX and Windows.

The standard output stream can be redirected to a file by using the redirect output symbol (>). For example, to redirect the output of program random to file out, use

$ random > out

Finally, program output can be appended to the end of an existing file by using the append output symbol (>>). For example, to append the output from program random to file out created in the preceding command line, use the command line

$ random >> out

14.3. Variable-Length Argument Lists

It’s possible to create functions that receive an unspecified number of arguments. Most programs in the text have used the standard library function printf, which, as you know, takes a variable number of arguments. As a minimum, printf must receive a string as its first argument, but printf can receive any number of additional arguments. The function prototype for printf is

int printf( const char *format, ... );

The ellipsis (...) in the function prototype indicates that the function receives a variable number of arguments of any type. The ellipsis must always be placed at the end of the parameter list.

The macros and definitions of the variable arguments headers <stdarg.h> (Fig. 14.1) provide the capabilities necessary to build functions with variable-length argument lists. Figure 14.2 demonstrates function average (lines 25–40) that receives a variable number of arguments. The first argument of average is always the number of values to be averaged.

Image

Fig. 14.1 stdarg.h variable-length argument-list type and macros.


 1   // Fig. 14.2: fig14_02.c
 2   // Using variable-length argument lists
 3   #include <stdio.h>
 4   #include <stdarg.h>
 5
 6   double average( int i, ... ); // prototype   
 7
 8   int main( void )
 9   {
10      double w = 37.5;
11      double x = 22.5;
12      double y = 1.7;
13      double z = 10.2;
14
15      printf( "%s%.1f %s%.1f %s%.1f %s%.1f ",
16         "w = ", w, "x = ", x, "y = ", y, "z = ", z );
17      printf( "%s%.3f %s%.3f %s%.3f ",
18         "The average of w and x is ", average( 2, w, x ),
19         "The average of w, x, and y is ", average( 3, w, x, y ),
20         "The average of w, x, y, and z is ",
21         average( 4, w, x, y, z ) );
22   } // end main
23
24   // calculate average
25   double average( int i, ... )
26   {
27      double total = 1; // initialize total
28      int j; // counter for selecting arguments
29      va_list ap; // stores information needed by va_start and va_end   
30
31      va_start( ap, i ); // initializes the va_list object   
32
33      // process variable-length argument list
34      for ( j = 1; j <= i; ++j ) {
35         total += va_arg( ap, double );
36      } // end for
37
38      va_end( ap ); // clean up variable-length argument list   
39      return total / i; // calculate average
40   } // end function average


w = 37.5
x = 22.5
y = 1.7
z = 10.2

The average of w and x is 30.000
The average of w, x, and y is 20.567
The average of w, x, y, and z is 17.975


Fig. 14.2 Using variable-length argument lists.

Function average (lines 25–40) uses all the definitions and macros of header <stdarg.h>. Object ap, of type va_list (line 29), is used by macros va_start, va_arg and va_end to process the variable-length argument list of function average. The function begins by invoking macro va_start (line 31) to initialize object ap for use in va_arg and va_end. The macro receives two arguments—object ap and the identifier of the rightmost argument in the argument list before the ellipsis—i in this case (va_start uses i here to determine where the variable-length argument list begins). Next, function average repeatedly adds the arguments in the variable-length argument list to variable total (lines 34–36). The value to be added to total is retrieved from the argument list by invoking macro va_arg. Macro va_arg receives two arguments—object ap and the type of the value expected in the argument list—double in this case. The macro returns the value of the argument. Function average invokes macro va_end (line 38) with object ap as an argument to facilitate a normal return to main from average. Finally, the average is calculated and returned to main.


Image Common Programming Error 14.1

Placing an ellipsis in the middle of a function parameter list is a syntax error—an ellipsis may be placed only at the end of the parameter list.


The reader may question how function printf and function scanf know what type to use in each va_arg macro. The answer is that they scan the format conversion specifiers in the format control string to determine the type of the next argument to be processed.

14.4. Using Command-Line Arguments

On many systems, it’s possible to pass arguments to main from a command line by including parameters int argc and char *argv[] in the parameter list of main. Parameter argc receives the number of command-line arguments that the user has entered. Parameter argv is an array of strings in which the actual command-line arguments are stored. Common uses of command-line arguments include passing options to a program and passing filenames to a program.

Figure 14.3 copies a file into another file one character at a time. We assume that the executable file for the program is called mycopy. A typical command line for the mycopy program on a Linux/UNIX system is

$ mycopy input output

This command line indicates that file input is to be copied to file output. When the program is executed, if argc is not 3 (mycopy counts as one of the arguments), the program prints an error message and terminates. Otherwise, array argv contains the strings "mycopy", "input" and "output". The second and third arguments on the command line are used as file names by the program. The files are opened using function fopen. If both files are opened successfully, characters are read from file input and written to file output until the end-of-file indicator for file input is set. Then the program terminates. The result is an exact copy of file input (if no errors occur during processing). See your system documentation for more information on command-line arguments. [Note: In Visual C++, you can specify the command-line arguments by right clicking the project name in the Solution Explorer and selecting Properties, then expanding Configuration Properties, selecting Debugging and entering the arguments in the textbox to the right of Command Arguments.]


 1   // Fig. 14.3: fig14_03.c
 2   // Using command-line arguments
 3   #include <stdio.h>
 4
 5   int main( int argc, char *argv[] )
 6   {
 7      FILE *inFilePtr; // input file pointer
 8      FILE *outFilePtr; // output file pointer
 9      int c; // define c to hold characters read from the source file
10
11      // check number of command-line arguments
12      if ( argc != 3) {
13         puts( "Usage: mycopy infile outfile" );
14      } // end if
15      else {
16         // if input file can be opened
17         if ( ( inFilePtr = fopen( argv[ 1 ], "r" ) ) != NULL ) {
18            // if output file can be opened
19            if ( ( outFilePtr = fopen( argv[ 2 ], "w" ) ) != NULL ) {
20               // read and output characters
21               while ( ( c = fgetc( inFilePtr ) ) != EOF ) {
22                  fputc( c, outFilePtr );
23               } // end while
24
25               fclose( outFilePtr ); // close the output file
26            } // end if
27            else { // output file could not be opened
28               printf( "File "%s" could not be opened ", argv[ 2 ] );
29            } // end else
30
31            fclose( inFilePtr ); // close the input file
32         } // end if
33         else { // input file could not be opened
34            printf( "File "%s" could not be opened ", argv[ 1 ] );
35         } // end else
36      } // end else
37   } // end main


Fig. 14.3 Using command-line arguments.

14.5. Notes on Compiling Multiple-Source-File Programs

It’s possible to build programs that consist of multiple source files. There are several considerations when creating programs in multiple files. For example, the definition of a function must be entirely contained in one file—it cannot span two or more files.

In Chapter 5, we introduced the concepts of storage classes and scope. We learned that variables declared outside any function definition are referred to as global variables. Global variables are accessible to any function defined in the same file after the variable is declared. Global variables also are accessible to functions in other files. However, the global variables must be declared in each file in which they’re used. For example, to refer to global integer variable flag in another file, you can use the declaration

extern int flag;

This declaration uses the storage-class specifier extern to indicate that variable flag is defined either later in the same file or in a different file. The compiler informs the linker that unresolved references to variable flag appear in the file. If the linker finds a proper global definition, the linker resolves the references by indicating where flag is located. If the linker cannot locate a definition of flag, it issues an error message and does not produce an executable file. Any identifier that’s declared at file scope is extern by default.


Image Software Engineering Observation 14.1

Global variables should be avoided unless application performance is critical because they violate the principle of least privilege.


Just as extern declarations can be used to declare global variables to other program files, function prototypes can extend the scope of a function beyond the file in which it’s defined (the extern specifier is not required in a function prototype). Simply include the function prototype in each file in which the function is invoked and compile the files together (see Section 13.2). Function prototypes indicate to the compiler that the specified function is defined either later in the same file or in a different file. Again, the compiler does not attempt to resolve references to such a function—that task is left to the linker. If the linker cannot locate a proper function definition, the linker issues an error message.

As an example of using function prototypes to extend the scope of a function, consider any program containing the preprocessor directive #include <stdio.h>, which includes a file containing the function prototypes for functions such as printf and scanf. Other functions in the file can use printf and scanf to accomplish their tasks. The printf and scanf functions are defined in other files. We do not need to know where they’re defined. We’re simply reusing the code in our programs. The linker resolves our references to these functions automatically. This process enables us to use the functions in the standard library.


Image Software Engineering Observation 14.2

Creating programs in multiple source files facilitates software reusability and good software engineering. Functions may be common to many applications. In such instances, those functions should be stored in their own source files, and each source file should have a corresponding header file containing function prototypes. This enables programmers of different applications to reuse the same code by including the proper header file and compiling their applications with the corresponding source file.


It’s possible to restrict the scope of a global variable or a function to the file in which it’s defined. The storage-class specifier static, when applied to a global variable or a function, prevents it from being used by any function that’s not defined in the same file. This is referred to as internal linkage. Global variables and functions that are not preceded by static in their definitions have external linkage—they can be accessed in other files if those files contain proper declarations and/or function prototypes.

The global variable declaration

static const double PI = 3.14159;

creates constant variable PI of type double, initializes it to 3.14159 and indicates that PI is known only to functions in the file in which it’s defined.

The static specifier is commonly used with utility functions that are called only by functions in a particular file. If a function is not required outside a particular file, the principle of least privilege should be enforced by using static. If a function is defined before it’s used in a file, static should be applied to the function definition. Otherwise, static should be applied to the function prototype.

When building large programs in multiple source files, compiling the program becomes tedious if small changes are made to one file and the entire program must be recompiled. Many systems provide special utilities that recompile only the modified program file. On Linux/UNIX systems the utility is called make. Utility make reads a file called makefile that contains instructions for compiling and linking the program. Products such as Eclipse™ and Microsoft® Visual C++® provide similar utilities as well.

14.6. Program Termination with exit and atexit

The general utilities library (<stdlib.h>) provides methods of terminating program execution by means other than a conventional return from function main. Function exit causes a program to terminate. The function often is used to terminate a program when an input error is detected, or when a file to be processed by the program cannot be opened. Function atexit registers a function that should be called upon successful termination of the program—i.e., either when the program terminates by reaching the end of main, or when exit is invoked.

Function atexit takes as an argument a pointer to a function (i.e., the function name). Functions called at program termination cannot have arguments and cannot return a value.

Function exit takes one argument. The argument is normally the symbolic constant EXIT_SUCCESS or the symbolic constant EXIT_FAILURE. If exit is called with EXIT_SUCCESS, the implementation-defined value for successful termination is returned to the calling environment. If exit is called with EXIT_FAILURE, the implementation-defined value for unsuccessful termination is returned. When function exit is invoked, any functions previously registered with atexit are invoked in the reverse order of their registration, all streams associated with the program are flushed and closed, and control returns to the host environment.

Figure 14.4 tests functions exit and atexit. The program prompts the user to determine whether the program should be terminated with exit or by reaching the end of main. Function print is executed at program termination in each case.


 1   // Fig. 14.4: fig14_04.c
 2   // Using the exit and atexit functions
 3   #include <stdio.h>
 4   #include <stdlib.h>
 5
 6   void print( void ); // prototype
 7
 8   int main( void )
 9   {
10      int answer; // user's menu choice
11
12      atexit( print ); // register function print   
13      puts( "Enter 1 to terminate program with function exit"
14         " Enter 2 to terminate program normally" );
15      scanf( "%d", &answer );
16
17      // call exit if answer is 1
18      if ( answer == 1 ) {
19         puts( " Terminating program with function exit" );
20         exit( EXIT_SUCCESS );
21      } // end if
22
23      puts( " Terminating program by reaching the end of main" );
24   } // end main
25
26   // display message before termination             
27   void print( void )                                
28   {                                                 
29      puts( "Executing function print at program "   
30         "termination Program terminated" );        
31   } // end function print                           


Enter 1 to terminate program with function exit
Enter 2 to terminate program normally
1

Terminating program with function exit
Executing function print at program termination
Program terminated



Enter 1 to terminate program with function exit
Enter 2 to terminate program normally
2

Terminating program by reaching the end of main
Executing function print at program termination
Program terminated


Fig. 14.4 Using the exit and atexit functions.

14.7. Suffixes for Integer and Floating-Point Literals

C provides integer and floating-point suffixes for explicitly specifying the data types of integer and floating-point literal values. (The C standard refers to such literal values as constants). If an integer literal is not suffixed, its type is determined by the first type capable of storing a value of that size (first int, then long int, then unsigned long int, etc.). A floating-point literal that’s not suffixed is automatically of type double.

The integer suffixes are: u or U for an unsigned int, l or L for a long int, and ll or LL for a long long int. You can combine u or U with those for long int and long long int to create unsigned literals for the larger integer types. The following literals are of type unsigned int, long int, unsigned long int and unsigned long long int, respectively:

174u
8358L
28373ul
9876543210llu

The floating-point suffixes are: f or F for a float, and l or L for a long double. The following constants are of type float and long double, respectively:

1.28f
3.14159L

14.8. Signal Handling

An external asynchronous event, or signal, can cause a program to terminate prematurely. Some events include interrupts (typing <Ctrl> c on a Linux/UNIX or Windows system), illegal instructions, segmentation violations, termination orders from the operating system and floating-point exceptions (division by zero or multiplying large floating-point values). The signal-handling library (<signal.h>) provides the capability to trap unexpected events with function signal. Function signal receives two arguments—an integer signal number and a pointer to the signal-handling function. Signals can be generated by function raise, which takes an integer signal number as an argument. Figure 14.5 summarizes the standard signals defined in header file <signal.h>.

Image

Fig. 14.5 signal.h standard signals.

Figure 14.6 uses function signal to trap a SIGINT. Line 15 calls signal with SIGINT and a pointer to function signalHandler (remember that the name of a function is a pointer to the beginning of the function). When a signal of type SIGINT occurs, control passes to function signalHandler, which prints a message and gives the user the option to continue normal execution of the program. If the user wishes to continue execution, the signal handler is reinitialized by calling signal again and control returns to the point in the program at which the signal was detected. In this program, function raise (line 24) is used to simulate a SIGINT. A random number between 1 and 50 is chosen. If the number is 25, raise is called to generate the signal. Normally, SIGINTs are initiated outside the program. For example, typing <Ctrl> c during program execution on a Linux/UNIX or Windows system generates a SIGINT that terminates program execution. Signal handling can be used to trap the SIGINT and prevent the program from being terminated.


 1   // Fig. 14.6: fig14_06.c
 2   // Using signal handling
 3   #include <stdio.h>
 4   #include <signal.h>
 5   #include <stdlib.h>
 6   #include <time.h>
 7
 8   void signalHandler( int signalValue ); // prototype   
 9
10   int main( void )
11   {
12      int i; // counter used to loop 100 times
13      int x; // variable to hold random values between 1-50
14
15      signal( SIGINT, signalHandler ); // register signal handler   
16      srand( time( NULL ) );
17
18      // output numbers 1 to 100
19      for ( i = 1; i <= 100; ++i ) {
20         x = 1 + rand() % 50; // generate random number to raise SIGINT
21
22         // raise SIGINT when x is 25
23         if ( x == 25 ) {
24            raise( SIGINT );
25         } // end if
26
27         printf( "%4d", i );
28
29         // output when i is a multiple of 10
30         if ( i % 10 == 0 ) {
31            printf( "%s", " " );
32         } // end if
33      } // end for
34   } // end main
35
36   // handles signal
37   void signalHandler( int signalValue )
38   {
39      int response; // user's response to signal (1 or 2)
40
41      printf( "%s%d%s %s",
42         " Interrupt signal ( ", signalValue, " ) received.",
43         "Do you wish to continue ( 1 = yes or 2 = no )? " );
44
45      scanf( "%d", &response );
46
47      // check for invalid responses
48      while ( response != 1 && response != 2 ) {
49         printf( "%s", "( 1 = yes or 2 = no )? " );
50         scanf( "%d", &response );
51      } // end while
52
53      // determine whether it's time to exit
54      if ( response == 1 ) {
55         // reregister signal handler for next SIGINT   
56         signal( SIGINT, signalHandler );               
57      } // end if
58      else {
59         exit( EXIT_SUCCESS );
60      } // end else
61   } // end function signalHandler


   1   2   3   4   5   6   7   8   9  10
  11  12  13  14  15  16  17  18  19  20
  21  22  23  24  25  26  27  28  29  30
  31  32  33  34  35  36  37  38  39  40
  41  42  43  44  45  46  47  48  49  50
  51  52  53  54  55  56  57  58  59  60
  61  62  63  64  65  66  67  68  69  70
  71  72  73  74  75  76  77  78  79  80
  81  82  83  84  85  86  87  88  89  90
  91  92  93
Interrupt signal ( 2 ) received.
Do you wish to continue ( 1 = yes or 2 = no )? 1
  94  95  96
Interrupt signal ( 2 ) received.
Do you wish to continue ( 1 = yes or 2 = no )? 2


Fig. 14.6 Using signal handling.

14.9. Dynamic Memory Allocation: Functions calloc and realloc

Chapter 12 introduced the notion of dynamically allocating memory using function malloc. As we stated in Chapter 12, arrays are better than linked lists for rapid sorting, searching and data access. However, arrays are normally static data structures. The general utilities library (stdlib.h) provides two other functions for dynamic memory allocation—calloc and realloc. These functions can be used to create and modify dynamic arrays. As shown in Chapter 7, a pointer to an array can be subscripted like an array. Thus, a pointer to a contiguous portion of memory created by calloc can be manipulated as an array. Function calloc dynamically allocates memory for an array. The prototype for calloc is

void *calloc( size_t nmemb, size_t size );

Its two arguments represent the number of elements (nmemb) and the size of each element (size). Function calloc also initializes the elements of the array to zero. The function returns a pointer to the allocated memory, or a NULL pointer if the memory is not allocated. The primary difference between malloc and calloc is that calloc clears the memory it allocates and malloc does not.

Function realloc changes the size of an object allocated by a previous call to malloc, calloc or realloc. The original object’s contents are not modified provided that the amount of memory allocated is larger than the amount allocated previously. Otherwise, the contents are unchanged up to the size of the new object. The prototype for realloc is

void *realloc( void *ptr, size_t size );

The two arguments are a pointer to the original object (ptr) and the new size of the object (size). If ptr is NULL, realloc works identically to malloc. If ptr is not NULL and size is greater than zero, realloc tries to allocate a new block of memory for the object. If the new space cannot be allocated, the object pointed to by ptr is unchanged. Function realloc returns either a pointer to the reallocated memory, or a NULL pointer to indicate that the memory was not reallocated.


Image Error-Prevention Tip 14.1

Avoid zero-sized allocations in calls to malloc, calloc and realloc.


14.10. Unconditional Branching with goto

Throughout the text we’ve stressed the importance of using structured programming techniques to build reliable software that’s easy to debug, maintain and modify. In some cases, performance is more important than strict adherence to structured programming techniques. In these cases, some unstructured programming techniques may be used. For example, we can use break to terminate execution of a repetition structure before the loop-continuation condition becomes false. This saves unnecessary repetitions of the loop if the task is completed before loop termination.

Another instance of unstructured programming is the goto statement—an unconditional branch. The result of the goto statement is a change in the flow of control to the first statement after the label specified in the goto statement. A label is an identifier followed by a colon. A label must appear in the same function as the goto statement that refers to it. Figure 14.7 uses goto statements to loop ten times and print the counter value each time. After initializing count to 1, line 11 tests count to determine whether it’s greater than 10 (the label start: is skipped because labels do not perform any action). If so, control is transferred from the goto to the first statement after the label end: (which appears at line 20). Otherwise, lines 15–16 print and increment count, and control transfers from the goto (line 18) to the first statement after the label start: (which appears at line 9).


 1   // Fig. 14.7: fig14_07.c
 2   // Using the goto statement
 3   #include <stdio.h>
 4
 5   int main( void )
 6   {
 7      int count = 1; // initialize count
 8
 9      start: // label   
10
11         if ( count > 10 ) {
12            goto end;
13         } // end if
14
15         printf( "%d  ", count );
16         ++count;
17
18         goto start; // goto start on line 9   
19
20      end: // label   
21         putchar( ' ' );
22   } // end main


1  2  3  4  5  6  7  8  9  10


Fig. 14.7 Using the goto statement.

In Chapter 3, we stated that only three control structures are required to write any program—sequence, selection and repetition. When the rules of structured programming are followed, it’s possible to create deeply nested control structures from which it’s difficult to escape efficiently. Some programmers use goto statements in such situations as a quick exit from a deeply nested structure. This eliminates the need to test multiple conditions to escape from a control structure. There are some additional situations where goto is actually recommended—see, for example, CERT recommendation MEM12-C, “Consider using a Goto-Chain when leaving a function on error when using and releasing resources.”


Image Performance Tip 14.1

The goto statement can be used to exit deeply nested control structures efficiently.



Image Software Engineering Observation 14.3

The goto statement is unstructured and can lead to programs that are more difficult to debug, maintain and modify.


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

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