Logged in as: guest Log in
Lab 6 mcmillan / Version 7

 

Lab 6: A Program in Many Parts

Pre-Lab: 

As before, ensure that you can access your CS account on a Linux server.  In this lab, you will be doing more than just C programming, so it is necessary that you use a real system (and not a web app).

Optional: Many of you are probably getting tired of using Pico by now.  Pico is an extremely simple editor, and it is also extremely limited.  This lab is a good opportunity to try out Emacs (or Vim).  These are full fledged editors and would serve you well.  Look up a good tutorial and command reference for one of these and try creating/editing/searching a text file on the server.

Part 1: 

Previously, we've seen some C syntax for data types, control structures, input, output, and available mathematical operations. Some of these functions, such as printf() and fabs(), were available to us through a library.  To access these functions, we had to 1) add a preprocessor directive telling our compiler to load a header file and 2) use a linker flag to tell the compiler to link our code with the library object code.  

Header files are also useful for our own programs.  In essence, a header file is a definition of the interface for some subset of code.  Previously, we used header files to specify the interface to a system library, like "stdio.h". Now, we'll consider using header files to specify interfaces for our own code.

Having interfaces allows us to split up our code into multiple files while keeping our project managable.  Let's make a program with two parts.  First, create a file with two functions called "module.c" and creat the following two functions in it:

    int addOne(int a) {
        return a + 1;
    }

    float addPointOne(float b) {
        return b + 0.1;
    }

Next, create a second file with a main function, called "lab6.c". In your main function call the functions in "module.c" with the arguments 5 and 6.0 respectively and print the result.

Compile your code using

    gcc lab6.c module.c -o lab6

Checkoff 1: What is the warning that you get? What are valuses are printed by your program?

What happened was that the compiler did not know the type of the returned values from the functions that your wrote.  In main(), these functions had not been seen before.  When the C compiler encounters a function it has never seen, it assumes it will return an integer. Thus, addOne should work as we expect, but addPointOne should have problems, as would any function that does not return an integer.

Part 2:

We next need to include an interface definition in our lab6.c file so that the compiler knows the types returned by the functions we want to use.  First, let's establish some terminology.  A definition of a function creates code for the specified function in assembly alnguse.  A declaration of a function states that a function exists, that it has certain arguments, and the type of the values that it returns, but does not generate any code.  A call invokes a function, which jumps and links to the beginning of the assembled code where ever it is ocated in memory.  We can only write one definition for a function, otherwise we would be trying to create two blocks of code in memory with the same name.  We can create as many declarations of the function as we want: this only tells the compiler that a certain function exists.  We can also call a function as much as we want.

What we need is for our function, addPointOne(), to have a declaration in our main file.  It would also be good to have a declaration of addPointOne at the beginning of module.c so that we never have to worry about the order in which the functions are defined, and we would want a declaration in any new file that we create where addPointOne() is called.  We obviously do not want to write the same line over and over, so the solution to our problem is to create a header file, which we can then include in other files. 

Create a new file called "module.h". In it, write a declaration of addPointOne.  A declaration specifies three things:

  1. the type of the value returned by the function
  2. the function's name
  3. the types for each of the function's arguments in parentheses (the name is not even required)
  4. a semicolon

Next add a second #include directive to main.c following the one for stdio.h as follows:

#include "module.h"

Recompile your program (gcc lab6.c module.c -o lab6).

Checkoff 2:  Run the resulting code. Does it work as expected this time?

The preprocessor does little more than including the contents of a header file in your C file.  In fact, we can see precisely what it does.  Compile your code with the -E flag and direct the output to a new C file:

    gcc -E lab6.c > new.c

Next examine the contents of new.c in an editor and look for the header file you created. Notice that a significant fraction of the included lines are function declarations like the one you created in "module.h".

Checkoff 3:  Find the declaration for the "printf()" function. What is the type of printf's return value?

Part 3:

C files can also be separtely compiled and linked. This allows a commonly used module to be compiled once and later linked into several different programs. To illustrate this run the following two commands:

$ gcc -c lab6.c
$ gcc -c module.c

Examine the contents of the current directory. Notice that there are two new object files, lab6.o and module.o.

Next, we will examine the unresolved symbols in each of these two object files. This is done by dumping the symbol tables of each using the nm program. First, execute:


$ nm module.o

This should print the contents of the object file's symbol table. Notice that an address offset is provided for each of the functions and varibles defined in the modules. Labels that refer to code are indicated by a "T", and Labels to data are indicated by "D". The compiler also includes some auxillary data in the symbol table that saves the line numbers where each function is declared in the C source file for error handling indicated by "S". Also notice that the compiler has added an underscore to each of the function names.

Now dump the symbol table of the "lab6.o" file.

Checkoff 4:  How are unresolved symbols indicated in the symbol table? Hint: They should have no associated offset in the file. Which symbols referenced in lab6.o are unresolved?

Part 4:

Finally, we will link our two object modules together to make an executable file. This is also accomplished using the gcc program using only object files as input as follows:


$ gcc -o lab6b lab6.o module.o
To verify that the linking process worked execute the following command:

$ lab6b 

Have a good break! We will build a computer when you get back.




Site built using pyWeb version 1.10
© 2010 Leonard McMillan, Alex Jackson and UNC Computational Genetics