Importance of header files
When using a function from a library, C
allows you the option of ignoring the header file and simply declaring the
function by hand. In the past, people would sometimes do this to speed up the
compiler just a bit by avoiding the task of opening and including the file (this
is usually not an issue with modern compilers). For example, here’s an
extremely lazy declaration of the C function printf( ) (from
<stdio.h>):
printf(...);
The ellipses
specify a
variable
argument
list[34],
which says: printf( ) has some arguments, each of which has a type,
but ignore that. Just take whatever arguments you see and accept them. By using
this kind of declaration, you suspend all error checking on the
arguments.
This practice can cause subtle problems.
If you declare functions by hand, in one file you may make a mistake. Since the
compiler sees only your hand-declaration in that file, it may be able to adapt
to your mistake. The program will then link correctly, but the use of the
function in that one file will be faulty. This is a tough error to find, and is
easily avoided by using a header file.
If you place all your function
declarations in a header file, and include that header everywhere you use the
function and where you define the function, you ensure a consistent declaration
across the whole system. You also ensure that the
declaration and the definition
match by including the header in the definition file.
If a struct is declared in a
header file in C++, you must include the header file everywhere a
struct is used and where struct member functions are defined. The
C++ compiler will give an error message if you try to call a regular function,
or to call or define a member function, without declaring it first. By enforcing
the proper use of header files, the language ensures
consistency in libraries, and reduces bugs by forcing the same interface to be
used everywhere.
The header is a contract between you and
the user of your library. The contract describes your data structures, and
states the arguments and return values for the function calls. It says,
“Here’s what my library does.” The user needs some of this
information to develop the application and the compiler needs all of it to
generate proper code. The user of the struct simply includes the header
file, creates objects (instances) of that struct, and links in the object
module or library (i.e.: the compiled code).
The compiler enforces the contract by
requiring you to declare all structures and functions before they are used and,
in the case of member functions, before they are defined. Thus, you’re
forced to put the declarations in the header and to include the header in the
file where the member functions are defined and the file(s) where they are used.
Because a single header file describing your library is included throughout the
system, the compiler can ensure consistency and prevent
errors.
There are certain issues that you must be
aware of in order to organize your code
properly and write effective
header files. The first issue concerns what you can put into header files. The
basic rule is “only declarations,” that is,
only information to the compiler but nothing that allocates storage by
generating code or creating variables. This is because the header file will
typically be included in several translation units in a project, and if storage
for one identifier is allocated in more than one place, the linker will come up
with a multiple definition error (this is C++’s
one definition rule: You
can declare things as many times as you want, but there can be only one actual
definition for each thing).
This rule isn’t completely hard and
fast. If you define a variable that is “file
static” (has visibility only within a file) inside
a header file, there will be multiple instances of that data across the project,
but the linker won’t have a
collision[35].
Basically, you don’t want to do anything in the header file that will
cause an ambiguity at link time.