CS/CE 218 Lecture -*- Outline -*- Connection: Functions are an important modularity tool in C programming. Now we'll look a second tool: separate compilation of code contained in different files. * separate-compilation Advert: Separate compilation is a feature of C that makes it far better than Pascal for large programs. We'll look at the basics of dividing up your program into separate files today, and see how to get the parts to communicate. ** Compiler options (Unix book, section 14.1) *** compile and link Compilation turns C into object code (by way of assembler) Linking makes all the functions and data in your program work together, tells each function where the other functions are Suppose have a file roman.c #include #define ROWS 4 /*...*/ int pows[ROWS][COLS] = { /*...*/ }; int main(int argc, char *argv[]) { extern void checknum(int); extern void to_roman(int, char []); int low; char roman[25]; /*...*/ checknum(low) /*...*/ to_roman(low, roman); } void checknum(int value) { /* ...fprintf(...) ...*/ } void to_roman(int decimal, char roman[]) { /*... ROWS ...*/ } Q: How do you compile and link a program, all contained in ``roman.c''? gcc roman.c produces file a.out gcc -o roman roman.c produces file roman note gcc -O roman.c produces optimized version of a.out *** saving results of file compilation Q: Why would you want to save the results of compiling a single file? so when make a mistake don't have to recompile everything... cf. Pascal philosophy Imagine each of the functions in separate files roman.c ----------------- #include #define ROWS 4 /*...*/ int pows[ROWS][COLS] = { /*...*/ }; int main(int argc, char *argv[]) { extern void checknum(int); extern void to_roman(int, char []); int low; char roman[25]; /*...*/ checknum(low) /*...*/ to_roman(low, roman); } ------------------- checknum.c ----------------- #include void checknum(int value) { /* ...fprintf(...) ...*/ } ------------------ to_roman.c ----------------- #define ROWS 4 /*...*/ extern int pows[ROWS][COLS]; void to_roman(int decimal, char roman[]) { /*... ROWS ...*/ } ------------------ Q: How do you compile checknum.c to get an object file? gcc -c checknum.c produces checknum.o gcc -c *.c produces checknum.o roman.o to_roman.o Q: What happens if you execute ``gcc roman.c'', where roman.c is the file with the main program? you get an error from the linker (ld), that the symbols checknum and to_roman are undefined. if you compile gcc to_roman.c, get an error because _main is not defined. *** linking process that brings together separately compiled files into an executable (resolves external references) You can use ld but best to use gcc (or cc) Q: How do you use gcc to do link roman.o, checknum.o, to_roman.o? gcc -o roman *.o produces executable roman gcc *.o produces executable a.out Q: Suppose you use the square root routine from the math library, how do you tell ld or gcc to link with that library? gcc -o roman *.o -lm -lm gets library /lib/libm.a -lc gets library /lib/libc.a -lFOO gets library /lib/libFOO.a ** external variables are visible across files (section 4.3 in C book) (draw picture) Q: What does external mean? What does internal mean? names may be defined within or outside a function function names always external Q: How do you get the effect of a Pascal global variable in C? external variables An automatic variable is local to a function, space allocated on each entry Q: What properties distinguish external variables from automatic vars? external has greater scope (larger area of program) external has longer lifetime (permanent) Good to use external variables if 2 functions share the same data, yet neither calls the other (gee, this is an ADT) *** an example (in the book, see pages 75-79) This example different from the book's it's from a shell pre-processer Idea: copy file to standard output, replacing lines of the form includeonce(filename) with the text of the included file name. The string "includeonce(" must appear in as first characters of the line to be recognized. Includes can be nested. Files are only replaced the first time they are included. e.g., if cmd.sh contains includeonce(args-required.sh) includeonce(cmd-body.sh) and args-required.sh contains if test $# -eq 0 then echo "$USAGE" >&2 exit 1 fi and cmd-body.sh contains includeonce(args-required.sh) echo $2 $1 then shpp &2 exit 1 fi echo $2 $1 psuedo-code: while (not end of file) if line doesn't start with "includeonce(" copy it to stdout else /* the line does start with includeonce( */ if it has been included ignore it else open the file and do all this (recurse) record the file as included The ADT here is the "string_set" operations void string_set_insert(char []) int string_set_has(char []) To do the recursion need structure as follows /* #includes */ /* #defines */ extern void shpp(FILE *); main() { /* ... */ shpp(stdin); } /* string_set external variables */ void string_set_insert(char []) { /* ... */ } int string_set_has(char []) { /* ... */ } void shpp(FILE *fp) { /*... string_set_has(buf) ...*/ } Look at the specification of the string set ops: void string_set_insert(char s[]) /* EFFECT: put a copy of s in the set */ int string_set_has(char s[]) /* EFFECT: is s in the set? */ how is the set created? When program starts it's empty. Decide how to represent the set use an array of strings, keep track of how many in the array ensure that no duplicates get in the array #define MAX_TRACKED_INCLUDES 1000 #define MAX_PATHNAME_LENGTH 500 char already_included[MAX_TRACKED_INCLUDES] [MAX_PATHNAME_LENGTH]; int num_tracked = 0; Note that these are *external* variables! Also note initialization of num_tracked Write the code: void string_set_insert(char s[]) /* EFFECT: put a copy of s in the set */ { int i; for (i=0; i? "file.h" search *starts* in current directory search does not include current directory usually found in standard place /usr/include can add more standard places: gcc -I$HOME/include file.c Q: If a header file is changed, what do you have to recompile? files that include it. Q: Can you compile a header file? no (doesn't make sense, it's a part of another file, compiler doesn't recognize .h suffix) *** macro substitution (section 4.11.2) -------------- #define TRUE 1 #define FALSE 0 -------------- Q: What are the parts of a macro definition? name, and replacement text Q: Can the repolacement text take up more than one line? only if continue with backslash \ Q: What are some pitfalls in defining macros? need to enclose args in parentheses #define add(A, B) A + B 3 * add(5, 6) ==> 3 * 5 + 6 each use of parameter replaced by argument text (not value!) #define min(A, B) ((A) > (B) ? (A) : (B)) causes problems if arguments have side effects min(i++, 4) ==> ((i++) > (4) ? (i++) : (4)) Q: What are some advantages of macros over functions? polymorphism, works for any type of argument reduces run-time overhead for very short functions Q: Can you do exercise 4-14? *** conditional inclusion (section 4.11.3) helps to make code portable (!) also commonly used to make sure header files are only included once... Q: How do you ensure that a header file is only included once in C? #if !defined(MYFILE_H) #define MYFILE_H /* ... */ #endif