Team-FLY |
The C preprocessor, cpp , preprocesses C source code so that the C compiler itself does not have to worry about certain things. For example, say a C program has a line such as the following. #define BUFSIZE 250 In this case, cpp replaces all instances of the token BUFSIZE by 250 . The C preprocessor deals with tokens, so it does not replace an occurrence of BUFSIZE1 with 2501 . This behavior is clearly needed for C source code. It should not be possible to get cpp into a loop with something like the following. #define BUFSIZE (BUFSIZE + 1) Various versions of cpp handle this difficulty in different ways. In other situations, the program may not be dealing with tokens and might replace any occurrence of a string, even if that string is part of a token or consists of several tokens. One method of handling the loops that may be generated by recursion is not to perform any additional test on a string that has already been replaced . This method fails on something as simple as the following statements. #define BUFSIZE 250 #define BIGGERBUFSIZE (BUFSIZE + 1) Another way to handle this situation is to make several passes through the input file, one for each #define and to make the replacements sequentially. The processing can be done more efficiently (and possibly in parallel) with a pipeline. Figure 7.10 shows a four-stage pipeline. Each stage in the pipeline applies a transformation to its input and then outputs the result for input to the next stage. A pipeline resembles an assembly line in manufacturing. Figure 7.10. Four-stage pipeline.
This section develops a pipeline of preprocessors based on the ring of Program 7.1. To simplify the programming, the preprocessors just convert single characters to strings of characters .
Each stage of the pipeline reads from its standard input and writes to its standard output. You can generalize the problem by having each stage run execvp on an arbitrary process instead of calling the same function. The conf.in file could contain the command lines to execvp instead of the table of string replacements specific to this problem. It is also possible to have the original parent handle both the generation of pipeline input and the removal of its output. In this case, the parent opens file.in and file.out after forking its child. The process must now handle input from two sources: file.in and its standard input. It is possible to use select to handle this, but the problem is more complicated than might first appear. The process must also monitor its standard output with select because a pipe can fill up and block additional writes. If the process blocks while writing to standard output, it is not able to remove output from the final stage of the pipeline. The pipeline might deadlock in this case. The original parent is a perfect candidate for threading. Threads are discussed in Chapters 12 and 13. |
Team-FLY |