CSPP 50101 - Lab 7

In this Lab: Lab 7 is due: Saturday, Aug 13 at 11:59pm

Files for this lab are located in: /stage/classes/archive/2011/summer/50101-1/lab/lab7/src

Note that the src directory contains two subdirectories, myfileio and mypoint. You can copy both subdirectories and their contents with cp -r:

$ mkdir lab7
$ cd lab7
$ cp -r /stage/classes/archive/2011/summer/50101-1/lab/lab7/src/* . 
$ ls 
myfileio mypoint

References:

Here are some resources on the tool make that we will cover today:

Separate Compilation

Change to the myfileio directory for this part of the lab. You can see that this directory contains some of the files that you used in Lab 6:
$ cd myfileio
$ ls
demo.c  in1.txt  Makefile  myfileio.h  myfileio.c  test_ex1.c
Copy your version of myfileio.c from last week's lab into the myfileio directory. You will use this source file for this section of the lab.

In Lab 6 you saw how to organize source code into different files. In particular, you added functions to a small library called myfileio, implemented with files myfileio.h and myfileio.c. Then you tested this library with separate driver programs (ex: demo.c, test_ex1.c). To build a program that tests this library, you used this gcc command:

gcc -Wall -std=c99 myfileio.c demo.c -o demo
With this gcc command, if the programmer updates either myfileio.c or demo.c then both source code files must be recompiled to rebuild the executable demo. In large software projects, recompiling all source code files for every single change can be very time-consuming.

Another approach is separate compilation, where .c source files are compiled separately into object files and then linked to create a single executable. Here we will walk through the steps for separate compilation to build the program "demo" from the last lab.

First, call gcc as follows. Note the use of option -c:

$ gcc -Wall -std=c99 -c myfileio.c 
$ gcc -Wall -std=c99 -c demo.c 
Here option -c tells gcc to compile source code without creating an executable. The output is instead stored in an object file with a .o extension. Object files contain machine code; however they can not be executed as stand-alone programs. You can see the .o object files created by these calls to gcc in the current directory:
$ ls *.o
demo.o myfileio.o 

Next, run this gcc command to link object files into a new executable. (Note .o files are used as input to gcc, not .c files):

$ gcc demo.o myfileio.o -o demo
Recall that the driver program demo.c calls the function show_file(), which is implemented in another file, myfileio.c. So we have a call and implementation of a function represented in two different object files. At a high level, linking matches the reference to show_file() in object file demo.o with its implementation in myfileio.o.

You can now execute the program demo at the command line, just as you did in last week's lab:

$ ./demo in1.txt
Calling show_file() ...
100
110
120
125
130
One advantage of separate compilation is that is that you do not need to recompile all sources files as you update your code. Let's say you would like to build test_ex1 which also uses functions in myfileio.c. Since we have not updated myfileio.c we do not need to recompile it. All we need to do is compile test_ex1.c and then link to build the executable test_ex1:
$ gcc -Wall -std=c99 -c test_ex1.c
$ gcc test_ex1.o myfileio.o -o test_ex1
$ ./test_ex1 in1.txt
Number of lines in in1.txt: 5

If we update file myfileio.h or myfileio.c then the object file myfileio.o must be rebuilt. So we say that myfileio.o depends on myfileio.h and myfileio.c. When working with large software projects, it can be very cumbersome to remember which object files must be recompiled. To manage this process, programmers use makefiles to describe dependencies among files, and ensure that a file is recompiled only when its dependent files are changed. Below we will show how to use make to build the executable file test_ex1 in a single step.

make and Makefiles

In this section you will learn how to use the tool make that automates the compilation process for programs using multiple source files.

Basics: Calling make

You use make by creating a file named Makefile for your program. (Note that the myfileio directory already contains a Makefile). A Makefile is just a text file that contains rules that describe how the program should be compiled.

Users run make with commands such as the following:

  $ make target

This command searches the current directory for a file named Makefile, and then tries to update target if it is out of date. You will read more about rules and targets below.

If no target is specified, as shown here:

  $ make

then make will use the first one in the makefile.

Follow these steps to rebuild the program test_ex1. First remove object files and executables that you already built above:

$ rm *.o
$ rm test_ex1 
Run make as shown above (without a target). You will see that the program test_ex1 is built in the current directory.
$ make
gcc -g -Wall    -c -o myfileio.o myfileio.c
gcc -g -Wall    -c -o test_ex1.o test_ex1.c
gcc -g myfileio.o test_ex1.o -o test_ex1
$ ls test_ex1
test_ex1
Read through the following overview of make and Makefiles. You do not have to make any edits to the Makefile in the myfileio directory until you get to Exercise 0.

Rules

Next we will cover how to read and write Makefiles. Before you start, take a look at the Makefile in the myfileio directory (using cat or a text editor).

A Makefile contains rules that have the following format:

target: prerequisite1 prerequisite2 ...
	command
Here, target is usually the name of a file that will be updated by this rule. The list prerequisite1 prerequisite2 ... gives files that target depends on. On the second line, command shows how to update the target when one of the prerequisite files are modified.

As an example, we can write this rule to compile the object file myfileio.o:

# compile myfileio.o
myfileio.o: myfileio.c myfileio.h
	gcc -g -Wall -c myfileio.c 
By listing myfileio.c and myfileio.h as the prerequisites, we tell make that myfileio.o must be recompiled whenever myfileio.c or myfileio.h is updated. To ensure that this happens correctly, make will check the timestamps on all files and recompile myfileio.o when either of the source files has a newer timestamp. If myfileio.o is not out of date then it will not be recompiled.

Command lines in a makefile must start with a tab character, not a sequence of spaces. So in this example gcc is preceded by a tab.

Lines that start with # are comments and will be ignored. Lines containing only whitespace are also ignored.

Our makefile should also include these two rules to build our program:

# compile test_ex1.o
test_ex1.o: test_ex1.c myfileio.h
	gcc -g -Wall -c test_ex1.c -o test_ex1.o
 
# link test_ex1
test_ex1: test_ex1.o myfileio.o
	gcc myfileio.o test_ex1.o -o test_ex1
 

You can use multiple lines for one command by ending the previous line with a backslash ("\").

Phony targets: all and clean

The command line need not be a compilation instruction; instead it can be any shell command, or even empty. These targets are known as phony targets

It is conventional to include a target named "all" to update all outputs of the makefile:

# top-level rule
all: test_ex1
This rule has no command, instead it will call the rule with target "test_ex1" that we defined above. It is common to make "all" the default target, so that all outputs are updated when the user calls make without specifying a target (i.e., just entering "make" at the command line).

Recall that we can make "all" the default by listing it as the first rule in the makefile.

It is also common to include a "clean" target to remove outputs of the makefile such as executables and .o files:

clean:
	rm -f test_ex1.o myfileio.o test_ex1
Here we use the -f flag with rm to avoid generating error messages when a file does not exist.

The command make clean will do what we expect as long as we don't have a file in the current directory named "clean". To avoid problems when this file does exist, use the special target .PHONY to indicate that "clean" is not the name of an actual file:

.PHONY: clean  
clean:
	rm -f test_ex1.o myfileio.o test_ex1

Variables

You can define variables in a Makefile so that it is easier to update parameters that appear in multiple commands. For example, we can add these variables to tell make to use gcc as the compiler and "-Wall -g" as the compiler options:
# use "gcc" to compile source files
CC = gcc
# set compiler flags
CFLAGS = -Wall -g
We then update our rules to use these variables. To access the value of a variable use $( ) as shown below.
# top-level rule
all: test_ex1
 
# compile myfileio.o
myfileio.o: myfileio.c myfileio.h
	$(CC) $(CFLAGS) -c myfileio.c
 
# compile test_ex1.o
test_ex1.o: test_ex1.c myfileio.h
	$(CC) $(CFLAGS) -c test_ex1.c
 
# link test_ex1
test_ex1: test_ex1.o myfileio.o
	$(CC) $(LDFLAGS) myfileio.o test_ex1.o -o test_ex1
 
# remove outputs
.PHONY: clean	
clean:
	rm -f test_ex1.o myfileio.o test_ex1

Variables can be set from the command line, in which case they override the definitions in the makefile:

$ make "CFLAGS=-g" 
gcc -g   -c -o myfileio.o myfileio.c
gcc -g   -c -o test_ex1.o test_ex1.c
gcc -g myfileio.o test_ex1.o -o test_ex1
Note that "-g" is used instead of "-g -Wall" as set in the Makefile.

Automatic variables and implicit rules

In addition to variables that you define, there are several automatic variables that can be used in commands. Some of the most common are:

Automatic variables come in handy especially when writing implicit rules. Implicit rules are extremely useful tools to specify how to deal with all files with the same suffix. For example, the rule

%.o : %.c
	$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
specifies how to create an object file n.o from source file n.c. Note how no particular target is mentioned, instead the '%' character is used to match with any object file that ends in .o.

There are a number of "built-in" implicit rules that are always available unless you override them specifically. You can review these rules in the GNU Manual. In particular, note that:

The myfileio directory of this lab contains the Makefile for the program described above. In particular, note that these rules do not have a command line:

# compile - relies on predefined rule for .c files
myfileio.o: myfileio.c myfileio.h
 
test_ex1.o: test_ex1.c myfileio.h 
Here, myfileio.c will be compiled to object file myfileio.o using built-in rules. Likewise test_ex1.c will be compiled to output test_ex1.o. Since myfileio.h is included in the prerequisite lists, myfileio.o and test_ex1.o will both be recompiled when myfileio.h is updated.

Open the file myfileio.h and add a space character at the end of the file. This will update the timestamp on myfileio.h. Then, call make again. You will see that test_ex1 is rebuilt:

$ make
gcc -g -Wall    -c -o myfileio.o myfileio.c
gcc -g -Wall    -c -o test_ex1.o test_ex1.c
gcc  -g -Wall  myfileio.o test_ex1.o -o test_ex1 
Next remove the executable test_ex1 and call make again. You will see that the executable file is rebuilt but object files are not recompiled. This is because make detects that the prerequisites of myfileio.o and test_ex1.o were not updated since these files were last recompiled.
$ rm test_ex1
$ make
gcc  myfileio.o test_ex1.o -o test_ex1 

Exercise 0

Make the following changes to Makefile in the directory myfileio: You do not have to submit your changes for Exercise 0.

Structures

For the next exercise you use C structures which are compound data types, or groupings of simple data types with a predefined order. As an example, we represent a point in two-dimensional space as a single structure with two double members:
   struct mypoint
   {
     double x;
     double y;
   };
This structure declaration shows how a mypoint object is represented in memory. Note that structure declarations end with a semicolon (";"). It is a common compile-time error to forget the semicolon at the end of a structure definition.

A variable of type struct mypoint can be declared as:

   struct mypoint p1;
The members of a mypoint variable can be accessed with the "." (dot) operator. For example:
   struct mypoint p1;
   p1.x=1.0;
   p1.y=5.0;
You can also initialize a structure with a comma-separated list:
   struct mypoint p1 = {1.0,5.0};

You can declare pointers to structures. For example, a pointer to p1 would be:

   struct mypoint * p_p1 = &p1;
The -> operator is used to access structure members with a pointer.
   printf("Point p1: (%.2f,%.2f)\n",p_p1->x,p_p1->y);
The expressions p_p1->x and (*p_p1).x evaluate to the same value.

You can read more about structures in Chapter 14 of Prata.

The mypoint directory includes the file simple.c, a simple program that declares and uses the mypoint structure as shown above.

Exercise 1

Code for this exercise is in the mypoint directory which you copied above. Change this this directory now. You will see these files: For this exercise you will write a program that outputs the distance between two points. You must make the following changes: You will need to use the math library to complete this exercise; to link to the math library use option -lm with gcc.

Exercise 2

Build a Makefile for your distance program. You can use the Makefile from myfileio as a starting point. Your makefile should meet these requirements for full credit: Here is sample output from a correct Makefile:
$ make clean
rm -f mypoint.o distance.o distance
$ make
gcc -g -Wall -std=c99   -c -o mypoint.o mypoint.c
gcc -g -Wall -std=c99   -c -o distance.o distance.c
gcc -lm mypoint.o distance.o -o distance
$ ./distance 1 5 4 9
Distance between (1.00,5.00) and (4.00,9.00): 5.00
Your output may be slightly different so long as the above requirements are met.

Submitting your assignment

Lab 7 is due: Saturday, Aug 13 at 11:59pm

You may submit the contents of the mypoint directory for this lab. But, call make clean before you submit! This should remove files mypoint.o, distance.o, and distance.

I will grade your work in these files:

Be sure that every source file you submit includes this information in comments at the top of the file:

Also include a simple README file in your lab7 directory with your name, username, and any other comments or questions for the Lab TA.

Do not submit any compiled binary files

You will submit this lab with the hwsubmit program.

$ hwsubmit cspp50101lab <path to your lab7 directory>

Submit to cspp50101lab, NOT cspp50101.


prepared by Sonjia Waxmonsky