For the purposes of this course a module is defined to be either:
The header file defines the interface to the module. It contains the prototypes for the functions the module exports (makes available) to other modules and to the main program file. It may also export #define directives and type declarations if such things are needed to use the exported functions.
The implementation file contains definitions for the functions listed in the header file, plus whatever code is necessary to support these function definitions. Typically, the supporting code will consist of #define directives, type declarations for use only within the implementation file, declarations of static global variables, and prototypes and definitions of static functions. All of this supporting code is private to the module. As far as the main program and other modules are concerned, it is hidden information.
If you have worked with Turbo Pascal unit files, it may be helpful to note that a single Turbo Pascal unit file plays a very similar role to that played by the pair of files that make up a C module. The interface section of the unit is like the header file of the C module, and the implementation section of the unit is like the implementation file of the C module.
The interface to a module is like the front panel of the vending machine. The front panel allows the customer to perform a limited number of operations: to check whether the machine has run out of the customer's preferred kind of drink; to insert coins; to retrieve change; and to press a button to select which kind of drink the machine will deliver.
The implementation of a module is like the inside of the vending machine, which contains drinks, a system for storing and dispensing drinks, refrigeration equipment, money, a system to sort coins, count money and dispense change, and so on. What all of this stuff looks like and how it works is hidden from the customer. The customer trusts the machine to respond correctly to coin insertion and drink selection without knowing exactly how that will happen. This is a good example of information hiding.
The benefit of information hiding to the vending machine customer is simple but important:
The machine makes it easy to buy a drink.The customer doesn't have to worry about details of money counting or motors and gears or temperature control.
Information hiding has many benefits for the vending machine owner. Here are two important ones:
Protection of system integrity. Because of the limited number of controls on the front panel it is hard for customers to inadvertently (or maliciously) foul up the operation of the machine. There is no way a customer can cause the cans of Diet Glorp to be replaced with cans of Sugary Glorp, there is no way a customer can change the price of a drink to 15 cents, and so on.Easy modification of internals. Customers do not rely on knowledge of the insides of the machine when they buy drinks. If the owner changes the insides of the machine, for example improving its energy efficiency or reconfiguring drink storage to make more room for the more popular flavours of drink, the customers, unaware of the internal changes, will continue to use the machine the way they always have.
The above discussion of vending machines should be easy to understand, but transferring the ideas back to software design may be difficult. It's fairly easy to think about the different physical parts of a vending machine, even if (like me) you don't really know what those parts look like. It's a bit harder to think of a computer program as a collection of components, because the components are conceptual instead of physical.
Another problem students have in learning to think in terms of modules is that students in programming courses are usually asked to play the roles of designer, builder and user all at the same time. When you're writing function prototypes for a C header file you should have a radically different point of view from the point of view you have later when you're writing function definitions in an implementation file. Looking at one project from multiple points of view is a difficult skill that takes time to learn. It's worth learning not just for software development but for all branches of engineering and management.
A static function may be called only within the .c file where it is defined. In other words, it is private to a single implementation file. The role of a static function is to be an ``helper'' to one or more of the functions exported by a module. The keyword static must appear both in the function prototype and the function definition of a static function. Since a static function is not exported by a module, its prototype should appear in the implementation file, not the header file.
Access to a static global variable is shared by all of the functions in the .c file where the static global variable is declared. Functions in other .c files do not have access. Such global variables are required whenever a module manages data that must be ``remembered'' between calls to its exported functions. Because it is private to an implementation file, a static global variable is much less evil than a global variable that can be scribbled on by every single function in a program.
When applied to local variables, static has quite a different meaning. See Sections 8.1, 8.3, and 8.6 of your textbook for details on the different meanings of static.
top-of-file commentThe rules for the top-of-file comment are the same as for Labs 2-4: include the name of the file, your name, U of C ID number and lab section number, and the lab assignment number and exercise letter.#ifndef transmogrified file name
#define transmogrified file name#include directives (if necessary)
exported #define directives (if necessary)
exported struct and typedef declarations (if necessary)
prototypes and function interface comments for functions exported by the module
#endif /* transmogrified file name */
Function interface comments should be written in terms of restrictions on argument values and in terms of services provided by the module. Since information about how the functions work is supposed to be hidden, do not describe the behaviour of the function by referring to information in the implementation file.
The transmogrified file name is an identifier based on the name of the header file, altered to look like a preprocessor name. For example, fred.h would generate FRED_H as a transmogrified file name. The purpose of the #ifndef - #endif wrapper for the header file is to allow for repeat inclusion of header files. This protects against silly errors like
#include "bozo.h" #include "bozo.h"but that is not the main point. The main point is to protect against subtle errors like
#include "clown.h" #include "bozo.h"where the programmer is unaware that #include "clown.h" appears within the file bozo.h. Writing header files this way in ENGG 333 exercises may seem unnecessarily elaborate, but it's a very good habit to adopt if you plan to work with larger C programs in the future.
Remember that #define directives and type declarations should be exported only if they must be exported to support the functions exported by the module.
top-of-file commentAgain: the rules for the top-of-file comment are the same as for Labs 2-4.#include directives (must include own header file)
private #define directives (if necessary)
private struct and typedef declarations (if necessary)
prototypes for static functions (if necessary)
declarations of static global variables (if necessary)
function definitions
A module implementation file must include its own header file to ensure that function prototypes in the header file are consistent with function definitions in the implementation file.
Formal function interface comments are not required for static functions because formal documentation is unnecessary for functions that are not exported by a module. However, you are encouraged to write a function interface comment whenever it would be effective in explaining the role of a static function.
top-of-file commentOne more time: the rules for the top-of-file comment are the same as for Labs 2-4.#include directives (must include at least one module header file)
private #define directives (if necessary)
private struct and typedef declarations (if necessary)
prototypes for static functions (if necessary)
declarations of static global variables (if necessary)
function definition for main
definitions for static functions (if necessary)
The point of the example is not to show why modules are good, but simply to show how C modules are organized. (Most readers will agree that the program is rather silly.) The benefits of modules are not really apparent in a tiny program such as this. Upcoming lab exercises will let you work with or build more useful modules.
Header file: The module exports two functions: say_boo and change_boo_volume, so there are two function prototypes in the header file. There are also some #define directives. The #define directives appear in the header file and not the implementation file because they are needed by callers of the change_boo_volume function.
Implementation file: In addition to the say_boo and change_boo_volume functions, the implementation file contains some helper functions. The helper functions are private to the implementation, so they are declared to be static. There is a static global variable called volume_setting, which both say_boo and change_boo_volume need access to.
Defect detection code: The use of assert in the definition of change_boo_volume and the code belonging to the default case in the switch statement in say_boo are examples of what could be called ``defect detection code'' - code that helps programmers find program flaws. These two pieces of code both cause the program to crash in ways that offer programmers considerable information about what went wrong. They are not intended to deal with user errors, since the messages they generate will be meaningless to a typical user. The use of abort rather than exit is advantageous for programmers who wish to examine programs with a debugger like gdb.
Main program file: There's nothing that needs to be said about this file. Compile and run the program if you're not sure what it does.
The example program, although trivial, nevertheless provides an example of a cohesive module. The services listed in the header file boo.h are clearly closely related.
The standard library header file <string.h> is an example of a cohesive interface: all of the services provided are operations on character strings.
On the other hand the <stdlib.h> header file is not a very cohesive interface. It contains prototypes for the program termination functions abort and exit, for the dynamic memory manager functions malloc and free, for pseudorandom number functions, and for many other miscellaneous library functions.
(Note: I have avoided using the word module when referring to the library, because the source and object code organization of libraries is generally much more complex than that of the modules you will create or modify in ENGG 333.)
However, certain kinds of coupling are much better than others. Sharing constants and interacting by means of function calls are both acceptable forms of coupling.
There are at least two forms of coupling that should generally be avoided. Creating global variables that all modules can modify introduces a nasty form of coupling. So does creating functions that return pointers to the private data of modules. There are two main reasons why these forms of coupling are bad: