Week 2
Week 2
Week 2
s Compiling The Preprocessor Linking Access The Shell Make Header Files Compiling The Preprocessor Linking Access The Shell Make
Make
. . . . . . . . . . . .
. . . . . . . . . . . .
But then things get messy. . . Don’t Repeat Yourself (the DRY principle) Header File — Example
calc.c ▶ We could end up repeating the same declarations many times. calc.c
double square(double n) { return n * n; } double square(double n) { return n * n; }
▶ Say calc.c has 5 functions, and 10 other files use those
double cube(double n) { return n * n * n; } double cube(double n) { return n * n * n; }
functions.
▶ That’s 50 declarations.
main.c ▶ This is a very bad idea. calc.h (header file)
double square(double n); /* Declarations */ ▶ Copying and pasting is easy, but. . . double square(double n); /* Declarations */
double cube(double n); ▶ Changing those declarations is time consuming and prone to double cube(double n);
... mistakes.
result = square(5) + cube(5); ▶ So, we put all the declarations for calc.c in a “header file”. main.c
▶ When a file needs to use a function from calc.c, we write: #include "calc.h" /* Include header file */
aardvark.c
...
double square(double n); /* Repeated from above */ #include "calc.h" /* Note: .h not .c */
result = square(5) + cube(5);
double cube(double n);
... ▶ This takes the declarations in the header file calc.h and puts (Put “#include "calc.h"” in all files using square() or
printf("%lf %lf\n", square(x), cube(y)); . . . . them right here. . . . .
cube().) . . . .
. . . . . . . . . . . .
The #ifdef and #endif directives More Conditional Compilation Avoiding Multiple Inclusion
A segment of code is only compiled if a given name has been
#defined (“conditional compilation”). ▶ With the #include directive, some files may be included
multiple times.
Example ▶ Preprocessor names can be defined on the compiler
▶ This may cause problems in some situations.
command-line as well:
#define DEBUG 1
[user@pc]$ gcc main.c -o program -D DEBUG=1 ▶ Can be avoided using conditional compilation.
...
int i, sum = 0; ▶ #ifndef checks that a given name has not been defined. Example (where “. . . ” is the normal header file contents)
for(i = 0; i < 100; i++) { ▶ #else is available for convenience.
sum += i; #ifndef FILENAME_H
▶ #if and #elif are slightly more flexible versions. #define FILENAME_H
#ifdef DEBUG
▶ These all work like an if-else statement, but at compile ...
printf("%d ", sum);
#endif time (not run time). #endif
}
Only the first inclusion will count, because FILENAME_H will be
Without the first line, the preprocessor edits out the printf(). . . . . . . . .
defined from then on. . . . .
. . . . . . . . . . . .
The extern storage class The static storage class Static local variables
▶ extern can be used on both function and variable ▶ static can also be used on function and variable declarations. ▶ Ordinary local variables disappear when a function ends.
declarations. ▶ static local variables don’t disappear.
▶ Two distinct meanings, depending on where it occurs:
▶ It means that the definition occurs somewhere else. ▶ They keep their values between function calls.
▶ For functions, this is true anyway, so extern is redundant. 1. static makes a function (or global variable) inaccessible ▶ Initialised only once, when the function is first called.
▶ Consider these equivalent declarations: from outside this file.
Example
float theFunction(int x, int y); static void privateFunc(int x, int y) { ... }
void count(void) {
extern float theFunction(int x, int y); ▶ Good practice for functions that don’t need to be accessed static int counter = 0;
elsewhere. counter++;
The extern form often appears in header files, but only as a ▶ Vaguely similar to the “private” keyword in OO languages (like printf("%d\n", counter);
reminder. C++ or Java), but don’t confuse .c files with classes! }
▶ For variables, extern allows you to access global variables
2. static makes a local variable persistant throughout the
across files. This is both complicated and dangerous, so we program’s runtime. counter increases each time count() is called, and never resets
won’t waste our time on it! . . . . . . . . until the program ends. . . . .
. . . . . . . . . . . .
▶ A filename containing a “/” — an executable file to run: ▶ Particularly useful for commands/programs that open GUI varname="Some text"
windows: ▶ No spaces except inside quotes! Really.
[user@pc]$ ./program apple banana caroot
[user@pc]$ gedit somefile.txt & ▶ They are accessed using a $ sign:
▶ An executable file in the “search path”:
Here, you can still use the shell while gedit is running.
echo The string is $varname
[user@pc]$ ls -l Desktop ▶ Backgrounded programs can still output to the terminal, even
while you’re typing a command ▶ The shell will replace “$varname” with “Some text”.
▶ The shell searches a list of directories for the file “ls”. ▶ echo will then display “The string is Some text”.
▶ The directories to search are specified by a variable called PATH ▶ This can lead to confusion (just be aware of it!)
(typically containing /bin and /usr/bin). ▶ Use {. . . } around the variable name if necessary:
▶ Standard UNIX commands may be either builtins or files in echo ${varname}y stuff
the search path. . . . . . . . .
This will print out “Some texty stuff”. . . . .
. . . . . . . . . . . .
What Make Does What About the Other Rules? Makefiles and C
▶ To run make: ▶ A prerequisite in one rule might be a target in another (with ▶ Make is very powerful, but we’ll focus on a limited subset of it.
[user@pc]$ make its own prerequisites). ▶ To create a makefile for a C application, we’ll have:
▶ One rule (the first rule) to create the executable.
▶ Make looks for a file called “Makefile” (exactly), and reads it. endfile : middlefile1 middlefile2 ...
▶ One rule to create each object file.
▶ Make looks at the first rule, by default. commandA
▶ The executable’s prerequisites are the object (.o) files.
▶ This is usually fine, but you can specify a different target: ▶ Each .o file’s prerequisites are:
middlefile1 : startfile1 startfile2
[user@pc]$ make anothertarget ▶ A single .c file with the same name;
commandB
▶ Any .h files #included by the .c file;
▶ Make asks these questions: ... ▶ Any other .h files #included by those .h files, and so on.
▶ Does the target file exist? ▶ Why are all the .h files included in the prerequisites?
▶ If so, make runs the other rule as well.
▶ Is the target newer than all its prerequisites? ▶ If .h files change, we would like Make to recompile the .c file.
▶ Does the secondary target (“middlefile1” above) exist?
▶ If so, the target is “up-to-date”, and nothing happens. ▶ Is the secondary targer newer than its own prerequisites? ▶ The contents of the .h files (particularly macros and constants)
▶ If not, make will run the command(s) to (re)create it. ▶ Typically, most makefile rules are connected in this way. will affect the compiled result.
▶ Make just assumes the commands will create the target file. ▶ Make will create middlefile1 and endfile, in that order. ▶ No rules to create .c or .h files.
▶ You must provide the correct commands. . . . .
▶ Once created, make will update them as needed. . . . .
▶ These files are created by the programmer! . . . .
. . . . . . . . . . . .
Makefile Example Makefile Diagram Makefile Example – Rule for the Executable
▶ Say our C application has these files:
▶ First, we write a rule for making the executable:
main.c – contains the main function.
aardvark.c – contains aardvark-related functions. program : main.o aardvark.o narwal.o
aardvark.h – contains aardvark-related declarations. gcc main.o aardvark.o narwal.o -o program
narwal.c – contains narwal-related functions.
▶ We make “program”, given main.o, aardvark.o and
narwal.h – contains narwal-related declarations.
narwal.o.
▶ main.c and aardvark.c both contain this line: ▶ This is the linking step.
▶ At first, these .o files don’t exist, but we’ll create them too in
#include "aardvark.h"
other makefile rules.
▶ main.c and narwal.c both contain this line: ▶ Make will run the gcc command if:
▶ program does not exist, or
#include "narwal.h" ▶ program is older than any of the .o files.
. . . . . . . . . . . .
. . . . . . . . . . . .
Makefile Example – Rules for the Object Files (1) Makefile Example – Rules for the Object Files (2) Makefile Example – Put Together
▶ Next, we write a rule for each .o file. ▶ A makefile is a single file, so let’s see it altogether:
1. Create main.o, based on main.c and both .h files. program : main.o aardvark.o narwal.o
2. Create aardvark.o, based on aardvark.c/.h. ▶ The final two rules, for completeness.
gcc main.o aardvark.o narwal.o -o program
3. Create narwal.o, based on narwal.c/.h.
aardvark.o : aardvark.c aardvark.h
▶ For example: gcc -c aardvark.c -Wall -ansi -pedantic main.o : main.c aardvark.h narwal.h
main.o : main.c aardvark.h narwal.h gcc -c main.c -Wall -ansi -pedantic
gcc -c main.c -Wall -ansi -pedantic narwal.o : narwal.c narwal.h
gcc -c narwal.c -Wall -ansi -pedantic aardvark.o : aardvark.c aardvark.h
▶ This is the compiling step (“-c” for compile only; don’t link). gcc -c aardvark.c -Wall -ansi -pedantic
▶ Make will run this command if: ▶ aardvark.o does not depend on narwal.h (and vice versa).
▶ main.o does not exist, or ▶ They only #include one .h file each. narwal.o : narwal.c narwal.h
▶ main.o is older than main.c or either .h file. gcc -c narwal.c -Wall -ansi -pedantic
▶ Notice that the .h files are not part of the command.
▶ gcc knows about the .h files, because the .c file tells it. ▶ Typing “make” will run each of these commands, if needed.
▶ However, we’re not quite finished yet!
. . . . . . . . . . . .
. . . . . . . . . . . .
clean:
. . . . . . . .
rm -f $(EXEC) $(OBJ) . . . . . . . .
66/67 67/67