Chuck Allison is a regular columnist with CUJ and a software architect for the Family History Department of the Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C++ Standards Committee. Chuck can be reached on the Internet at allison@decus.org, or at (801)240-4510.
It has been 26 years since Edsger Dijkstra wrote his famous letter entitled "GOTO Statement Considered Harmful," which, as the title suggests, made a strong case for never using a branching construct in a program [1]. In another letter he wrote, "... for some time I knew that, as a programmer, I could live quite happily without any form of the goto statement, but ... in the meantime my considered opinion is that I cannot live happily with the goto statement." [2] There followed a notorious movement to eliminate the goto construct altogether. Professors refused to accept students' programming assignments if they contained a goto. Languages were designed without a goto construct. The upside of all this upheaval was that as an industry we learned to think "structured," that is, to organize our software in a more logical and maintainable fashion. The downside is that we abandoned a potentially useful construct sometimes a goto is just what the doctor ordered. (By the way, almost all current programming languages in widespread use support some form of goto).
In this article I will discuss the mechanisms available to you in C to structure the logic of your programs. These mechanisms include not only the goto-less constructs of structured programming, but also branching techniques that range from the simplistic to the sophisticated: break, goto, non-local jumps (with setjmp/longjmp), and signals.
Structured Programming
In 1966 Bohm and Jacopini proved mathematically that it is possible to express any algorithm in terms of only three constructs, along with an arbitrary number of boolean flags [3]. The three constructs are:
1. sequences of statements
2. alternation (e.g., if-else, switch)
3. repetition (e.g., while, for, do)
We usually call programs that use only these mechanisms structured programs. Unfortunately, when structured-design gurus began their hard-sell to the industry, most programming professionals were making a living with COBOL, FORTRAN IV, and assembly language, while BASIC was gaining popularity in educational settings. Of these languages, only COBOL had the else construct. Telling a FORTRAN programmer (such as me) that he shouldn't use a goto usually resulted in him telling you where to go. Besides, we were proud of our expert ability to cram complex logic into as few lines as possible who cared if the lame-brains couldn't decipher code of such "elegance?" We couldn't have done this Great Work without the goto P.J. Plauger has said, speaking of the structured programming revolution, "Us converts waved this interesting bit of news under the noses of the unreconstructed assembly-language programmers who kept trotting forth twisty bits of logic and saying, 'I betcha can't structure this.' Neither the proof by Bohm and Jacopini nor our repeated successes at writing structured code brought them around one day sooner than they were ready to convince themselves." [4]
Listing 1 shows a vintage BASIC program that plays the game of "Hi-lo." You think of a number between 1 and 100, and just tell the computer whether its guesses are high, low, or right on. As long you give honest responses, the binary search algorithm will find your number in seven guesses or less (because ceil(log2(100)) == 7). Compare this program to the C version in Listing 2. The BASIC version is one-third the size of the C version, but is it more readable? Is it as maintainable? Is the structure of the logic evident from the shape of the program? No, no, no!
As a consequence of Bohm and Jacopini's work, it became natural to use alternation and repetition as the basic tools of thought when designing a logical process. It soon was quite popular to describe procedures in structured prose, or pseudocode, a terse, verbal description governed by loops and conditions. With pseudocode, you describe conditionals much the same as in any of the languages that support the if-else construct:
if <condition> do this... else do thatTo choose from a group of related values, you can use the case statement:
case x of 1: do this... 2: do that... 3: do the other thing...Structured programming allows separate controls for the various flavors of loops. Most of the time you need a control that tests its condition before entering the loop body:
while <condition> do this...A for loop is good for processes that must execute a specific number of times, as in
for i = 1 to n do something dependent on ior for processing a sequence of items:
for each student on the roster calculate something...On rare occasion you might need a loop that tests its controlling condition after executing the loop body:
repeat get user input until input is in the range [1..5]Listing 3 shows a pseudocode description of a process to merge two sorted text files. This code reads a line from each file and prints to standard output the line that lexicographically precedes the other. The process repeats until it empties one of the files, at which point it just outputs the rest of the other file.
Careful Conditioning
It is important that you relate a loop's condition clause to your code's design in a direct and understandable way. To convince yourself that you have correctly coded the condition clause, insert a comment at the beginning of the loop body that states what the condition of the loop means in terms of the task to be completed or problem to be solved. If the comment clearly expresses the solution you had in mind, you've crafted an effective loop. Do the same thing at the end of the loop.
The implementation of the merge procedure in Listing 5 shows an example of this practice. (The input data is in Listing 4. ) You can easily extend the merge algorithm to process a larger number of files. At first glance you might think that the loop control statement
while (neither file has been exhausted)should become
while (no file has been exhausted)But if you have n files, when that loop finishes you will still have n-1 active files to continue merging. You need to stay inside the merge loop until only one file is still active:
while (number of active files > 1) etc.This condition clause requires that you keep a list of active files, from which you will delete an entry when its file runs out of input lines. The design now looks like this:
while (number of active files > 1) { determine which line comes next print that line if (the corresponding file is now empty) delete it from the list of active files else get a fresh line from the file } empty the remaining fileSince you have introduced a mechanism to track the active files, you might as well stay in the loop until the last file is empty, and there is nothing else to do. The pseudocode in Listing 6 reflects this change, as do the invariants in the implementation in Listing 7.
Branching
Even if it is possible to program without gotos, it is often convenient to branch out of the middle of a loop (a special case of a goto). Using C's break statement can help you avoid littering your code with extraneous loop control variables. For example, the variables done and found in Listing 2 exist only to terminate their loops. Most C programmers do without them (see Listing 8, where I use a break instead).
Suppose you decide to rewrite the if-else statements under the comment /* Narrow the search range */ as a switch statement:
/* Narrow the search range */ switch(response) { case 'L': lo = guess + 1; break; case 'H': hi = guess - 1; break; case 'Y': puts("What? Try again..."); break; default: break; /* This doesn't work! */ }Unfortunately the break under the default case exits the switch, not the loop. In this case you can use a goto or use the if-else construct as before.Exiting deeply nested loops with a goto can be much clearer than the boolean flag approach the purists advocate. Consider this excerpt that searches a 3-dimensional array for a certain value:
for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) for (k = 0; k < s; ++k) if (a[i][j][k] == x) goto found;found:
/* use i, j, and k here */A naive attempt at a non-branching version gives:
for (done = 0, i = 0; !done && i < n; ++i) for (j = 0; !done && j < m; ++j) for (k = 0; !done && k < s; ++k) if (a[i][j][k] == x) done = 1; /* Fix indexes (yuck!) */ --i, --j, --k;Even if you don't mind the way this code looks, it's poorly designed because it increments the loop variables beyond the correct values, since it passes control to the enclosing loops all the way to the top level, instead of exiting immediately.Using the goto as a multi-level loop exit can help restore things to a stable state after a serious error, as in:
main() { /* Some one-time initialization here */ recover: for (;;) { /* Some more initialization here */ /* Things get deeply nested... */ if (<things go crazy>) goto recover; } }This is a common technique in hardware control programs.
Non-Local Branching
What if you encounter an exceptional condition deep within a set of nested function calls and your recovery point is back in a function higher up in the call chain (like main)? What you need is a "super goto" that branches across functions. That's what the setjmp/longjmp mechanism is for. You record a point to return to with setjmp, and branch to it with longjmp. Here's the syntax:
#include <setjmp.h> jmp_buf recover; main() { volatile int i = 0; for(;;) { if (setjmp(recover) != 0) { /* Recover from error in f() */ } /* Get deeply nested... */ } return 0; } ... void f() { /* Do some risky stuff */ if (<things go crazy>) longjmp(recover,1); /* else carry on */ }A jmp_buf is an array that holds the system information necessary to restore execution at the setjmp point. (Obviously, a jump buffer must be global or passed as a pointer argument). When you call setjmp, the system stores the calling environment (such as the contents of the stack and instruction pointer registers, etc.) in recover. setjmp always returns zero when called directly. A call to longjmp restores the calling environment, so execution continues back at the point of the setjmp call. At this point, the program appears to return yet again from setjmp, with one difference from before: it now appears as if setjmp has returned the second argument from the longjmp call (in this case a 1). Note however that if you give longjmp a second argument of 0, setjmp still returns a 1 because C forbids setjmp to return a 0 upon return from a longjmp. In this one case the compiler drops in a "hard-wired" return value. Since a longjmp performs an alternate return from a function, it interrupts the normal flow of a program, so you should use it only to handle unusual conditions.You need to take a few precautions when using setjmp/longjmp. For one thing, the function containing the setjmp target must still be active (i.e., must not yet have returned to its caller), otherwise the calling environment will no longer be valid. And of course, it is a bad idea to have more than one setjmp target with the same jmp_buf variable (now that would be confusing!). Since calling environments typically involve registers, and since a compiler is free to store automatic variables in registers, you have no idea if an automatic object will have the correct value when you return from a longjmp. You should declare automatics in any function containing a setjmp with the volatile qualifier, which guarantees that the inner workings of longjmp will leave them undisturbed.
There is another consequence of not being able to trust automatic storage during a longjmp: the setjmp call itself must not appear in an expression that may generate a temporary object, such as an intermediate computation. For this reason a call to setjmp may only appear as the sole controlling expression in an if or switch statement, or as an operand in an equality expression. You cannot reliably assign the result of setjmp to another variable. The program in Listing 9, which deletes subdirectory trees, calls setjmp from within a switch statement. (If you're interested in the details of this algorithm, see "Code Capsules: File Processing, Part 2," CUJ, June 1993.).
Signals
A signal occurs when an unusual event interrupts the normal execution of a program, such as a divide-by-zero error or when the user presses the attention key (e.g., Control-C or DEL). The header <signal.h> defines the following "standard" signals:
SIGABRT abnormal termination (raised by abort()) SIGFPE computational exception (e.g., overflow) SIGILL invalid function image (e.g., illegal instruction) SIGINT interactive attention (e.g., Control-C) SIGSEGV attempt to access protected memory SIGTERM termination requestThese signals originated on the PDP=11 architecture under UNIX, and may not all apply to your environment. An implementation may also define other signals, or may ignore signals altogether, so signal handling is by nature non-portable.Signals come in two flavors: synchronous and asynchronous. A synchronous signal is one that your program raises, such as by dividing by zero, overflowing a floating-point operation, or issuing a call to the abort function. You can raise a signal explicitly with a call to raise, for example:
raise(SIGABRT);An asynchronous signal occurs due to forces external to your program, such as a user pressing the attention key.The default response to most signals is usually to abort the program, but you can either arrange for signals to be ignored or provide your own custom signal handlers. The following statement from Listing 10
signal(SIGINT,SIG_IGN);tells the environment to ignore any keyboard interrupt requests (Control-C on my machine). The keyboard input process still echoes the ^C token on the display, but it does not pass control to the default signal-handling logic that terminates the program. The statement
signal (SIGINT, SIG_DFL)restores the default behavior, so you can halt the program from the keyboard.The program in Listing 11 invokes the library function signal to install a function (abort_handler) to respond to SIGABRT. A signal handler must take an integer argument and return void. The first call to abort transfers control to abort_handler, passes SIGABRT as the integer argument (which I don't happen to use in this case), and prints the message
Abort signal interceptedWhen execution enters a signal handler, the environment considers the associated signal "handled" before the very first statement executes. This breaks any subsequent association between your handler and the signal, and restores the default handling. This means that if your handler returns, it will not get control back the next time that signal is raised. That is why when control resumes where it was interrupted in main, and it calls abort the second time, the program actually terminates. In practice an abort handler should not return but should terminate the program with a call to exit instead (in this example you certainly shouldn't call abort!). Likewise, if you return from a SIGFPE handler the results are undefined.If you want to handle a signal every time it occurs, your handler should reinstall itself every time it executes. As the SIGINT handler (ctrlc) in Listing 12 illustrates, you should do this at the beginning of an asynchronous signal handler. If you put it off until later in the function, the same signal can occur before you've reinstalled it, causing you to lose the signal to the system default handler. In fact, there is very little an asynchronous signal can do safely. The signal you're processing could have occurred during the execution of a library function. Since the Standard C library is not reentrant, it would be a bad idea to call that library function from your handler. The rule of thumb is: Don't call library functions from within a function that handles an asynchronous signal. Nor should you call any function that might raise another signal. As the ctrlc function shows, there are only two safe things you can do before returning from an asynchronous handler:
1. Call signal to reinstall the handler, and
2. Assign a value to a static object of type volatile sig_atomic_t.
sig_atomic_t is an integral type that is guaranteed not to be interrupted by a signal while it is being accessed. You can use such a value as a flag or counter when normal execution resumes. Although redundant, it is a good practice to place an explicit return statement at the end of a handler that returns, to distinguish it from one that halts the progam.
In spite of what the preceding paragraph says about using library functions, it is nevertheless routine practice in many environments to longjmp out of an asynchronous signal handler. It works fine under DOS and most of the time under UNIX. As a common example, Listing 13 shows the outline of a command-line interpreter, a program that reads a sequence of text commands and performs the indicated functions (similar to sh in UNIX or DOS's COMMAND.COM). If you press the attention key from deep within the logic of an internal command, you want to return to the command loop, not terminate the program. longjmp allows you to do this.
Exit Handlers
This is as good a time as any to talk about exit handlers. With the atexit function you can register up to 32 functions which will be called when a program exits normally (i.e., not via abort). The functions execute in the reverse order that you registered them. The program in Listing 15 (pseudocode shown in Listing 14) is an external sort, a program that sorts files larger than can fit in memory. It does this by breaking the large file into smaller subfiles that do fit in memory. After sorting all subfiles the program merges them to standard output using the algorithm from Listing 6. If the program halts prematurely due to some external error, it may fail to delete some of the temporary subfiles from memory. To clean up the mess, the exit handler, remove_temps, removes any left over subfiles from memory (atexit appears near the beginning of sort in the listing). Note the goto in the loop that processes the command-line options. This program allows you to mix options in a single argument as in:
vsort -ir file (same as vsort -i -r file)but expects any numeric characters to appear last in a token, as in:
vsort -n8 fileThe program ignores any options that follow a numeric string; for example, it ignores v in:
vsort -n8v fileThe goto serves as a multi-level break and continue at the same time, since it exits the loop that traverses a single argument to cycle on the loop that visits each argument. This program also illustrates the following concepts from previous Code Capsule articles:November 1992:
Variable-length formatting
In-core formattingApril 1993:
Sorting with qsortMay 1993:
Command-line arguments
Temporary file names
I/O redirection
Deleting filesAugust 1993:
Pointer arithmetic
Summary
The C language supports structured programming as follows:
Alternation: if-else, switch, conditional (ternary) expression
Repetition: while, for, do Clarity and economy sometimes require you to use the branching constructs:
Branching: break, continue, return, goto And once in a great while, you need the non-traditional control mechanisms that the C library provides:
Non-local branching: setjmp, longjmp
Signals: signal, raise The further you progress down the list, the more careful you have to be. Next month I'll explore C++'s contribution to flow control: exceptions.
References
1. E. Dijkstra, "goto Statement Considered Harmful," Communications of the ACM 11:3, p. 147, March 1968.
2. E. Dijkstra, "Stepwise Program Construction," printed in Selected Writings on Computing: A Personal Perspective, Springer-Verlag, 1982, p. 2.
3. C. Bohm & G. Jacopini, "Flow Diagrams, Turing Machines, and Languages with Only Two Formation Rules," Communications of the ACM 9:5, p. 266, May 1966.
4. P. J. Plauger, Programming on Purpose, Essays on Software Design, Prentice-Hall, 1993, p. 25.
Listing 1 A BASIC program to play HI-LO
100 rem A very old BASIC program to play HI-LO 110 print "Think of a number between 1 and 100" 120 print "If you don't cheat, I'll figure it out" 130 print "in seven guesses or less!" 140 lo = 1 150 hi = 100 160 if lo > hi then print "You cheated!" : goto 240 170 g = int((lo + hi) / 2) 180 print "Is it";g;" (L/H/Y)?" 190 input r$ 200 if r$ = "L" then lo = g+1 : goto 160 210 if r$ = "H" then hi = g-1 : goto 160 220 if r$ <> "Y" then print "What? Try again..." : goto 190 230 print "What fun!" 240 print "Wanna play again?" 250 input r$ 260 if r$ = "Y" then 140
Listing 2 A C program to play HI-LO
/* The game of HI-LO - your number in 7 guesses */ #include <stdio.h> #include <ctype.h> main() { int done 0; /* Control flag */ puts("Think of a number between 1 and 100."); puts("If you don't cheat, I'll figure it out"); puts("in seven guesses or less!\n"); while (!done) { int found = 0; /* Control Flag */ int lo = 1, hi = 100; int guess; char response; /* Play the Game */ while (!found && lo <= hi) { /* Get response */ guess = (lo + hi) / 2; printf("Is it %d (L/H/Y): ",guess); fflush(stdout); scanf("%c%*c",&response); response = toupper(response); /* Narrow the search range */ if (response == 'L') lo = guess + 1; else if (response == 'H') hi = guess - 1; else if (response != 'Y') puts("What? Try again..."); else found = 1; } /* Print results */ if (lo > hi) puts("You cheated!"); else { printf("Your number was %d\n",guess); puts("What fun!"); } printf("Wanna play again? "); fflush(stdout); scanf("%c%*c",&response); if (toupper(response) != 'Y') done = 1; } return 0; } /* Sample Execution (numbers are 37 and 99) c:>hi-lo Think of a number bewteen 1 and 100. If you don't cheat, I'll figure it out in seven guesses or less! Is it 50 (L/H/Y): h Is it 25 (L/H/Y): l Is it 37 (L/H/Y): y Your number was 37 What fun! Wanna play again? y Is it 50 (L/H/Y): l Is it 75 (L/H/Y): l Is it 88 (L/H/Y): l Is it 94 (L/H/Y): l Is it 97 (L/H/Y): l Is it 99 (L/H/Y): l Is it 100 (L/H/Y): h You cheated! Wanna play again? n */ /* End of File */
Listing 3 Pseudocode for a procedure to merge two text files
/* Program to merge two sorted files */ open and read first line from each file while (neither file has been exhausted) { determine which line comes next print that line get a fresh line from the corresponding file } empty the remaining active file, if any close files /* End of File */
Listing 4 Input files for Listing 5 and Listing 7
FILE1.DAT --------- brown fox quick the FILE2.DAT ---------- dog jumped lazy over the FILE3.DAT --------- and cow jumped moon over the the /* End of File */
Listing 5 C implementation of Listing 3
/* merge1.c: Merge two sorted files * to standard output */ #include <stdio.h> #include <stdlib.h> #include <string.h> main(int argc, char *argv[]) { FILE *f1, *f2; char buf1[BUFSIZ], buf2[BUFSIZ]; /* Open files */ if (argc != 3) return EXIT_FAILURE; f1 = fopen(argv[1],"r"); f2 = fopen(argv[2],"r"); if (!f1 || !f2) return EXIT_FAILURE; /* Do the merge */ fgets(buf1,BUFSIZ,f1); fgets(buf2,BUFSIZ,f2); while (!feof(f1) && !feof(f2)) { /* INVARIANT: both buffers * have fresh lines */ /* Print and refresh * the appropriate line */ if(strcmp(buf1,buf2)<=0> { fputs(buf1,stdout); fgets(buf1,BUFSIZ,f1); } else { fputs(buf2,stdout); fgets(buf2,BUFSIZ,f2); } } /* INVARIANT: At least one file * has been exhausted */ /* Empty the remaining file */ while (!feof(f1)) { /* INVARIANT: buffer-1 has * a fresh line */ fputs(buf1,stdout); fgets(buf1,BUFSIZ,f1); } /* INVARIANT: file1 has been exhausted */ fclose(f1); while (!feof(f2)) { /* INVARIANT: buffer-2 has * a fresh line */ fputs(buf2,stdout); fgets(buf2,BUFSIZ,f2); } /* INVARIANT: file2 has been exhausted */ fclose(f2); return EXIT_SUCCESS; } /* Sample execution: c:>merge1 file1.dat file2.dat brown dog fox jumped lazy over quick the the */ /*End of File */
Listing 6 Pseudocode for a procedure to merge an arbitrary number of files
/* Program to merge an arbitrary number * sorted files */ open and read first line from each file while (no file has been exhausted) { determine which lines comes next print that line if (the corresponding file is now at end-of-file) delete it from the list of active files else get a fresh line from the file } /* End of File */
Listing 7 C implementation of Listing 6
/* merge2.c: Merge up to 15 files to standard output */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXFILES 15 static FILE *mustopen(char *, char *); struct finfo { FILE *f; char line[BUFSIZ]; }; main(int argc, char *argv[]) { int i, min_idx; int nfiles = argc - 1; /* Number of active files */ struct finfo *files; if (nfiles == 0 || nfiles > MAXFILES) return EXIT_FAILURE; /* Open files - read first line */ files = calloc(nfiles,sizeof(struct finfo)); if (!files) return EXIT_FAILURE; for (i = 0; i < nfiles; ++i) { files[i].f = mustopen(argv[i+1],"r"); fgets(files[i].line,BUFSIZ,files[i].f): } /* Do the merge */ while (nfiles) { /* INVARIANT: At least one buffer has a fresh line */ /* Determine which lines comes next and print it */ for (min_idx = 0, i = 1; i < nfiles; ++i) if (strcmp(files[i].line,files[min_idx].line) < 0) min_idx = i; fputs(files[min_idx].line,stdout); /* Get a fresh line from the corresponding file */ if (!fgets(files[min_idx].line,BUFSIZ,files[min_idx].f)) { /* This file is finished */ fclose(files[min_idx].f); files[min_idx] = files[--nfiles]; } } /* INVARIANT: all files have been closed */ free(files); return EXIT_SUCCESS; } static FILE *mustopen(char *fname, char *mode) { FILE *f = fopen(fname,mode); /* Open file or die */ if (!f) { fprintf(stderr,"Can't open %s.\n",fname); exit(EXIT_FAILURE); } return(f); } /* Sample execution: c:>merge2 file1.dat file2.dat file3.dat and brown 7cow dog fox jumped jumped lazy moon over over quick the the the the */ /* End of File */
Listing 8 Removes extraneous loop controls from Listing 2
/* hi-lo2.c: Removes extraneous * boolean flags */ #include <stdio.h> #include <ctype.h> main() { puts("Think of a number between 1 and 100."); puts("If you don't cheat, I'll figure it out"); puts("in seven guesses or less!\n"); for(;;) /* An idiom for REPEAT FOREVER */ { int lo = 1, hi = 100; int guess; char response; /* Play the Game */ while (lo <= hi) { /* Get response */ guess = (lo + hi) / 2; printf("Is it %d (L/H/Y): ",guess); fflush(stdout); scanf("%c%*c",&response); response = toupper(response); /* Narrow the search range */ if (response == 'L') lo = guess + 1; else if (response == 'H') hi = guess - 1; else if (response != 'Y') puts("What? Try again..."); else break; } /* Print results */ if (lo > hi) puts("You cheated!"); else { printf("Your number was %d\n", guess); puts("What fun!"); } printf("Wanna play again? "); fflush(stdout); scanf("%c%*c",&response); if (toupper(response) ! = 'Y') break; } return 0; } /* End of File */
Listing 9 Recursive directory delete program that illustrates non-local branching
/* ddir.c: Remove subdirectory tree*/ #include <stdio.h> #include <stdlib.h> #include <io.h> #include <string.h> #include <sys/stat.h> #include <setjmp.h> #include <dirent.h> #include <dir.h> /* longjmp return codes */ #define BAD_DIR 1 #define DIR_OPEN_ERR 2 #define FILE_DEL_ERR 3 #define DIR_DEL_ERR 4 /* DOS-specific macros - change for other OS */ #define CMD_FORMAT "del *.* <%s > nul" #define CMD_LEN 17 /* Change this to "/" for UNIX */ char Response_file[L_tmpnam+1] = "\\"; /* Calling environment buffer */ jmp_buf env; main(volatile int argc, char **volatile argv) { FILE *f; volatile char *old_path = getcwd(NULL,64); void rd(char *); /* Create response file for DOS del command */ tmpnam(Response_file+1); if ((f = fopen(Response_file,"w")) == NULL) abort(); fputs("Y\n",f); fclose(f); switch(setjmp(env)) { case BAD_DIR: fputs("Invalid directory\n",stderr); break; case DIR_OPEN_ERR: fputs("Error opening directory\n",stderr); break; case FILE_DEL_ERR: fputs("Error deleting file\n",stderr); break; case DIR_DEL_ERR: fputs("Error deleting directory\n",stderr); break; } /* Delete the directories */ while (--argc) rd((char *) *++argv); /* Clean-up */ remove(Response_file); chdir((char *) old_path); return 0; } void rd(char* dir) { char sh_cmd[L_tmpnam+CMD_LEN]; DIR *dirp; struct dirent *entry; struct stat finfo; /* Log onto the directory that is to be deleted */ if (chdir(dir)) longjmp(env,BAD_DIR); printf("%s:\n",strlwr(dir)); /* Delete all normal files via OS shell */ sprintf(sh_cmd,CMD_FORMAT,Response_file); system(sh_cmd); /* Delete any remaining directory entries */ if ((dirp = opendir(".")) == NULL) longjmp(env,DIR_OPEN_ERR); while ((entry = readdir(dirp)) != NULL) { if (entry->d_name[0] == '.') continue; stat(entry->d_name,&finfo); if (finfo.st_mode & S_IFDIR) rd(entry->d_name); /* Subdirectory */ else { /* Enable delete of file, then do it */ chmod(entry->d_name,S_IWRITE); if (unlink(entry->d_name)) longjmp(env,FILE_DEL_ERR); } } closedir(dirp); /* Remove the directory from its parent */ chdir(".."); if (rmdir(dir)) longjmp(env,DIR_DEL_ERR); } /* End of File */
Listing 10 Turns off keyboard interrupt requests
/* ignore.c: Ignores interactive * attention key */ #include <stdio.h> #include <signal.h> main() { char buf[BUFSIZ]; int i; /* Ignore keyboard interruptions */ signal(SIGINT,SIG_IGN); while (gets(buf)) puts(buf); /* Restore default attention key handling so the user can abort form the keyboard */ signal(SIGINT,SIG_DFL); for (i = 1; ; ++i) printf("%d%c",i,(i%15 ? ' ' : '\n')); } /* Sample Execution c:>ignore hello hello ^C there there ^C ^Z 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 ^C */ /* End of File */
Listing 11 Intercepts the SIGABRT (abort) signal
/* abort.c: Handle SIGABRT */ #include <stdio.h> #include <signal.h> #include <stdlib.h> void abort_handler(int sig); main() { /* Install signal handler */ if (signal(SIGABRT,abort_handler) == SIG_ERR) fputs("Error installing abort handler\n",stderr); abort(); abort(); return 0; } void abort_handler(int sig) { fputs("Abort signal intercepted\n",stderr); } /* Output: Abort signal intercepted Abnormal program termination */ /* End of File */
Listing 12 A safe SIGINT handler that counts keyboard interrupts
/* ctrlc.c: A safe SIGINT handler */ #include <stdio.h> #include <signal.h> void ctrlc_handler(int sig); volatile sig_atomic_t ccount = 0; main() { char buf[BUFSIZ]; /* Install SIGINT handler */ if (signal(SIGINT,ctrlc_handler) == SIG_ERR) fputs("Error installing ctrl chandler\n",stderr); /* Do some I/O */ while (gets(buf)) puts(buf); /* Restore default handler */ signal(SIGINT,SIG_DFL); printf("You pressed ctrlc %d times\n,ccount); return 0; } void ctrlc_handler(int sig) { /* Re-install handler immediately */ signal(sig,ctrlc_handler); ++ccount; return; } /* Sample Execution c:>ctrlc hello hello ^C there there ^C ^Z You pressed ctrlc 2 times */ /* End of File */
Listing 13 A skeleton for a command interpreter illustrates branching out of a signal handler
/* shell.c: A skeleton for a command * interpreter */ #include <stdio.h> #include <signal.h> #include <setjmp.h> /*etc.*/ jmp_buf restart; void ctrlc(int sig); main() { /* Install signal handler */ signal(SIGINT,ctrlc); /* Return here on keyboard interrupt */ setjmp(restart); /* Do any other initialization here... */ for(;;) { int command_code; /* etc... */ /* Read and parse command here... */ /* Execute internal command here */ switch(command_code) { case 'Q': return 0; /* quit */ /* Lots of cases follow... */ } } } void ctrlc(int sig) { /* Reinstall handler */ signal(sig,ctrlc); /* Jump back to command line */ longjmp(restart,1); return; } /* End of File */
Listing 14 Pseudocode for an External Sort
/* Program to sort large files externally */ process command-line options: -i (ignore case) -nW (sort field is numeric, width W) -r (reverse sort order) -v (verbose mode) -N (sort field starts in column N) if (an input file was specified) redirect standard input to file while (an input line was successfully read) { if (buffer or memory exhausted) { sort/write lines in memory to temp file free memory } store line in memory } if (any temp files were created) { create last temp file from lines in memory merge temp files to standard output (see Listing 6) } else { /* All lines fit in memory */ sort lines in memory print lines to standard output } /* End of File */
Listing 15 An external sort that uses an exit handler
/* vsort.c: An simple, external sort/merge * * Divides large files into sorted subfiles, * if necessary, then merges them. * * OPTIONS: * -i ignore case * -nW sort numbers of width W * -r reverse (descending order) * -v verbose (display progress) * -N sort field starts in column N * (1-based) */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <math.h> #include <float.h> #define MAXLINES 1000 /* Watch your stack size! */ #define MAXFILES 15 #define MAXNUM DBL_DIG+9 /* Compare function for qsort */ int comp(const void *, const void *); /* Merge file info */ struct mfile { FILE *f; char name[L_tmpnam]; char line[BUFSIZ]; } files[MAXFILES]; /* Option control flags */ int width = MAXNUM, fold = 0, numeric = 0, reverse = 0, verbose = 0, start_col = 0, nfiles = 0, main(int argc, char **argv) { int i; void sort(void); /* Process command-line options */ for (i = 1; i <argc && *argv[i] == '-'; ++i) { char *p; for (p = argv[i]+1; *p; ++p) switch(tolower(*p)) { case 'i': fold = 1; break; case 'n': numeric = 1; width = atoi(p+1); if (width <= 0) width = MAXNUM; goto next_arg; case 'r': reverse = 1; break; case 'v': verbose = 1; break; default: if (isdigit(*p)) start_col = max(atoi(p)-1,0); else fprintf(stderr, "Bad option: %c\n",*p); goto next_arg; } next_arg: /* Break inner loop to cycle on outer */ ; } if (verbose) { /* Display options */ fprintf(stderr,"Start column = %d.\n", start_col+1); if (numeric) fprintf(stderr, "Sorting numerically (width =" "%d).\n",width); else if (fold) fputs("Case ignored in" "comparisons.\n",stderr); if (reverse) fputs("Sorting in descending order.\n",stderr); } /* Redirect standard input */ if (i < argc && freopen(argv[i],"r",stdin) == NULL) { fprintf(stderr,"Can't open %s.\n",argv[1]); exit(EXIT_FAILURE); } sort(); return EXIT_SUCCESS; } void sort(void) { static char buf[BUFSIZ], *lines[MAXLINES]; int i, nlines, min_idx; int merge_flag = 0; void make_mfile(char **, int, int); void remove_temps(void); if (verbose) fputs("Reading file...\n",stderr); /* Register exit handler */ atexit(remove_temps); /* Read input lines */ for (nlines = 0; fgets(buf,BUFSIZ,stdin); ++nlines) { if (nlines == MAXLINES || (lines[nlines] = malloc(strlen(buf)+1)) == NULL) { /* Store in temporary merge file */ merge_flag = 1; make_mfile(lines,nlines,nfiles++); lines[nlines = 0] = malloc(strlen(buf)+1); } strcpy(lines[nlines],buf); } if (merge_flag) { /* Form last merge file from remaining lines */ make_mfile(lines,nlines,nfiles++); if (verbose) fprintf(stderr,"Merge phase (%d runs)\n", nfiles); /* Open temporary files */ for (i = 0; i < nfiles; ++i) { files[i].f = fopen(files[i].name,"r"); if (files[i].f == NULL) { fputs("Temporary file error.\n",stderr); exit(EXIT_FAILURE); } /* Read first line */ fgets(files[i].line,BUFSIZ,files[i].f); } /* Do the merge */ while (nfiles) { /* Find next output line */ for (min_idx = 0, i = 1; i < nfiles; ++i) { char *s1 = files[i].line; char *s2 = files[min_idx].line; if (comp(&s1,&s2) < 0) min_idx = i; } /* Print it */ fputs(files[min_idx].line,stdout); /* Get the next line from this file */ if (fgets(files[min_idx].line, BUFSIZ, files[min_idx].f) == NULL) { /* This file is finished */ fclose(files[min_idx].f); if (verbose) fputs("Deleting temporary file\n", stderr); remove(files[min_idx].name); files[min_idx] = files[--nfiles]; } } } else { /* Sort singleton file and print it*/ if (verbose) fputs("Sorting...\n",stderr); qsort(lines,nlines,sizeof lines[0],comp); for (i = 0; i < nlines; ++i) { fputs(lines[i],stdout); free(lines[i]); } } } int comp(const void *xp, const void *yp) { char *x = *(char **)xp, *y = *(char **)yp; int result; if (numeric) { double diff; char xtemp[MAXNUM+1], ytemp[MAXNUM+1]; /* Compare numbers */ sprintf(xtemp,"%-.*s",width,x+start_col); sprintf(ytemp,"%-.*s",width,y+start_col); diff = atof(xtemp) - atof(ytemp); result = (diff < 0.0) ? -1 : (diff > 0.0) ? 1 : 0; } else if (fold) result = stricmp(x+start_col,y+start_col); else result = strcmp(x+start_col,y+start_col); return reverse ? -result : result; } void make_mfile(char **lin, int nl, int nf) { FILE *f; int i; if (nl == 0) return; /* Sort lines and store in a temporary file */ tmpnam(files[nf].name); if ((f = fopen(files[nf].name,"w")) == NULL) { fputs("Can't open temporary file.\n",stderr); exit(EXIT_FAILURE); } qsort(lin,nl,sizeof lin[0],comp); if (verbose) fprintf(stderr, "Writing temporary file %d...\n", nf); for (i = 0; i < nl; ++i) { fputs(lin[i],f): free(lin[i]); } fclose(f); files[nf].f = NULL; } void remove_temps(void) { int i; /* Delete stragglers at exit */ for (i = 0; i < nfiles; ++i) { if (files[i].f) fclose(files[i].f); if (verbose) fputs("Deleting temporary file.\n", stderr); remove(files[i].name); } } /* End of File *