Section 7k

Using Header Files


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Clean Modularity

Breaking Down the Code

Up to this point, since way back in Chapter 0, one single file has been used to manage all the source code. This form of management is functional for relatively small programs, but when programs start becoming larger and more complex, it can be very helpful from a readability and management standpoint to break the code implementation away from the other supporting data such as included library header files, constants and defined data structures, and function prototypes. This topic will review single-file programs, discuss using header files, and take a small dip into multiple-file programs.

Up To This Point

For a quick review of what single-file programs look like, and why they look like they do, here are a couple of examples, with descriptions.

Since the compiler must know how developed functions are specified before it can pass through the running code, there must be a header in some form for it to use in order to verify that a given function in use has been correctly formatted and created. Thus the original form of C programs commonly looked like the following.

// header files

// defined data types

// defined constants

// function implementations
// i.e., each entire function, written out

// the main program

The identifying condition, and commonly the problem with this format is that all the functions are implemented (i.e., completely written out) prior to the main function, so in a relatively non-trivial program, the main function (i.e., the actual program) could be a few thousand lines down toward the end of the file. This is functional and does work; it's just not very easy to see what the program is doing.

For that reason, a prototype, or forward declaration, can be placed before the main function so the compiler can still see what the function syntax should look like but without taking up nearly as much space. The prototype is simply the header of the function with a semicolon at the end of it. This form of a program would look like the following.

// header files

// defined data types

// defined constants

// function prototypes
// i.e., just the header of each function

// the main program

// function implementations
// preferably in alphabetical order for easy access

This allows a program reviewer to quickly get to the main function and figure out what the program does. This format is commonly used by programmers who are working with larger programs.

However, programs are going to get larger and more complicated. And to maintain readability and reasonable modularity, there are solutions for that.

Header Files

There are a few reasons to use header files.

First, by moving some of the supporting code actions, such as included library files, constants, data structures, and prototypes, into a separate header file, it can shorten the length of the actual implementation file.

Next, if the program code is of reasonable quality, other programmers are likely to use the code, which is good. However, when the other programmers want to look up what functions are available, or identify the form or syntax of the functions, they can quickly review the prototypes in the header file rather than having to dig through all the implementation code.

Finally, it is not uncommon for one set of code to be used in multiple files. It is much faster, and consumes much less overhead if the constants, data types, prototypes, etc. can be included in the other code set rather than all of the implementation of the code. Remember, the compiler only needs to see these things, including the prototypes, to know if the data and/or functions are being used correctly in any other programming file.

For reference, the header file has an extension of ".h" (e.g., "headerfile.h") and the implementation file has the extension that has already been used to this point ".c" (e.g., "implementationfile.c").

Now consider the organization format that will be used in these two files.

Header File.

// protection from multiple compile actions
#ifndef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H

// all necessary header and library files
// to be included in the program

// defined data types

// defined constants

// function prototypes
// i.e., just the header of each function

// end multiple compile protection
#endif // HEADER_FILE_NAME_H

This should look pretty clear except for the multiple compile protection. That component will be discussed later.

Implementation File

// one header file inclusion
// - the associated .h file

// the main program

// function implementations
// preferably in alphabetical order for easy access

Note that there is no multiple compile protection in the implementation file. Again, this will be discussed later.

And Combined

Compiling this code will be identical to the way the original source code file has been up to this point. The header file is included in the implementation file so it does not need to be addressed in the compiling action, as shown here.

gcc -Wall programfile.c -o testprog

That is all there is to managing header files along with the implementation file. And note that header files for C++ code are identical in form. The one difference that is not really a difference is that C++ header files will have the class definition code in the header file, however that is just placed where the above example shows "defined data types" so it isn't really different.

That said, there is one more consideration when the programs really become complicated.

Multiple File Programs

When programmers start getting into really non-trivial programs at scale, multiple files will be created, compiled, and linked to create a program. Once again, this is commonly implemented to support modularity. For example, a large-scale program might use several string operation tools, which are combined in one pair of files and labelled StringUtilities.h and StringUtilities.c. In addition, the program might conduct a significant amount of statistical analyses so it will also have StatsUtilities.h and StatsUtilities.c files to manage those tools.

Then maybe there are several user interface tools used to support command line input from, and output to, the user. This file might be called ConsoleUtilities.h and ConsoleUtilities.c.

All these tools are in separated files so if one is updated or debugged, it can be handled without bothering with any of the other code utilities. That is one of the pillars of modularity.

Finally, there will be some kind of main driver file that holds the main function, and usually not much more since the necessary working tools are in the other utilities files.

Compiling and linking these files is not terribly complicated, and not much more than was provided above. The other files simply need to be added into the compile command, as shown here.

gcc -Wall StringUtilities.c StatsUtilities.c
ConsoleUtilities.c maindriver.c -o testprog

To be clear, all of this text would be on one line but was broken up to fit on this page. And this should be familiar to all students who have been using ConsoleUtilities.c (and .h) already.

And The Header File Protection?

As presented previously, there must be a consideration for protecting header files from being compiled multiple times. Think about the StringUtilities file. First, it will use (include) the StringUtilities.h file in the StringUtilities.c file; that pretty much always happens. However, it is also likely that the StringUtilities tools will be needed in the ConsoleUtilities file as well as the main driver file.

This means that the StringUtilities.h file will be included in three different other header files, and of course, the compiler, whose job it is to compile, will attempt to compile the header file all three times. This will go badly. The compiler will generate a large number of errors, mostly saying something about duplicate or repeated definition of the items in the StringUtilities.h file. And the compiler will be correct to complain; only one set of definitions of the constants, data structures, and prototypes needs to be defined.

The solution to this is to tell the compiler to only compile the header files just once, using code that is called precompiler directives, which is a fancy way of saying, "these code statements will tell the compiler what to do while it is compiling".

Consider the precompiler directives that were provided previously, this time with some description.

The result of these precompiler directives is that the header file will only be compiled once. After the first time, the HEADER_FILE_NAME_H will be identified as "defined" and the header file will not be compiled any more.

Problem solved. A programmer can include a given header file in as many files as need that particular set of tools without concern for compiler revenge.

Header Files - The Finale

For programs that multiply two numbers together or translate Celsius into Fahrenheit, there just isn't much need to structure or organize program code. However, programmers don't get paid well (or really at all) for these kinds of programs. Real programs have multiple files, use high quality structure, and implement modularity at every turn. Or they don't work. That is the difference between professional and amateur programming. Stay on top of this.