Programming Utilities Guide

Compiling Other Source Files

Compiling and Linking a C Program with Assembly Language Routines

The makefile in the next example maintains a program with C source files linked with assembly language routines. There are two varieties of assembly source files: those that do not contain cpp preprocessor directives, and those that do.

By convention, assembly source files without preprocessor directives have the .s suffix. Assembly sources that require preprocessing have the .S suffix.


Note -

ASFLAGS passes options for as to the .s.o and .S.o implicit rules.


Assembly sources are assembled to form object files in a fashion similar to that used to compile C sources. The object files can then be linked into a C program. make has implicit rules for transforming .s and .S files into object files, so a target entry for a C program with assembly routines need only specify how to link the object files. You can use the familiar cc command to link object files produced by the assembler:

Table 4-17 Summary of Macro Assignment Order
CFLAGS= -O 
ASFLAGS= -O 

.KEEP_STATE:

driver: c_driver.o s_routines.o S_routines.o 
        	cc -o driver c_driver.o s_routines.o
         S_routines.o

Note that the .S files are processed using the cc command, which invokes the C preprocessor cpp, and invokes the assembler.

Compiling lex and yacc Sources

lex and yacc produce C source files as output. Source files for lex end in the suffix .l, while those for yacc end in .y. When used separately, the compilation process for each is similar to that used to produce programs from C sources alone.

There are implicit rules for compiling the lex or yacc sources into .c files; from there, the files are further processed with the implicit rules for compiling object files from C sources. When these source files contain no #include statements, there is no need to keep the .c file, which in this simple case serves as an intermediate file. In this case you could use .l.o rule, or the .y.o rule, to produce the object files, and remove the (derived) .c files.

For example, the makefile:

CFLAGS= -O 
.KEEP_STATE:

all: scanner parser 
scanner: scanner.o 
parser: parser.o

produces the result shown below.

$ make -n
rm -f scanner.c 
lex -t scanner.l > scanner.c 
cc -O -c -o scanner.o scanner.c 
rm -f scanner.c
yacc parser.y 
cc -O -c -o parser.o y.tab.c 
rm -f y.tab.c

Things become more complicated when you use lex and yacc in combination. In order for the object files to work together properly, the C code from lex must include a header produced by yacc. It might be necessary to recompile the C source file produced by lex when the yacc source file changes. In this case, it is better to retain the intermediate (.c) files produced by lex, as well as the additional .h file yacc provides, to avoid running lex whenever the yacc source changes.


Note -

yacc produces output files named y.tab.c and y.tab.h. If you want the output files to have the same basename as the source file, you must rename them.


The following makefile maintains a program built from a lex source, a yacc source, and a C source file.

CFLAGS= -O 
.KEEP_STATE:

a2z: c_functions.o scanner.o parser.o 
        	cc -o $@ c_functions.o scanner.o parser.o 
scanner.c: 

parser.c + parser.h: parser.y 
        	yacc -d parser.y 
        	mv y.tab.c parser.c 
        	mv y.tab.h parser.h

Because there is no transitive closure for implicit rules, you must supply a target entry for scanner.c. This entry bridges the gap between the .l.c implicit rule and the .c.o implicit rule, so that the dependency list for scanner.o extends to scanner.l. Since there is no rule in the target entry, scanner.c is built using the .l.c implicit rule.

The next target entry describes how to produce the yacc intermediate files. Because there is no implicit rule for producing both the header and the C source file using yacc -d, a target entry must be supplied that includes a rule for doing so.

Specifying Target Groups with the + Sign

In the target entry for parser.c and parser.h, the + sign separating the target names indicates that the entry is for a target group. A target group is a set of files, all of which are produced when the rule is performed. Taken as a group, the set of files comprises the target. Without the + sign, each item listed would comprise a separate target. With a target group, make checks the modification dates separately against each target file, but performs the target's rule only once, if necessary, per make run.