Monday 3 December 2007

C header definition and declaration

Structuring header files

  > In the past, I've been told that for every .c file, you should
> have a corresponding .h file which will contain all the necessary
> definitions, prototypes, etc... for the functions in the file.
> Is this the case, or should we just use one global header file
> that contains all the prototypes, externs, defines, etc...

The rules are not carved in stone, and should be followed with a grain of
salt in all specific cases, but here's the usual story:

a) It's always a good idea to play safe against possible multiple inclusions
of the same .h file. This can happen if you #include "foo.h" ,"bar.h", and
bar.h has an "#include "foo.h" line itself.
The canonical way to prevent multiple inclusions is to wrap each header
with an #ifndef CONSTANT directive, and #define that constant in the file
(immediately after). Example:

/* This is myheader.h */
#ifndef _MYHEADER_
#define _MYHEADER_

/* All the rest of the header here */

#endif /* _MYHEADER_ */
/* End of myheader.h */

The preprocessor will access the "rest of the header" only the first time
the header is included. The second time the _MYHEADER_ constant will be
already defined, causing #ifndef to be false, and the "rest" to be skipped
up to the #endif /* _MYHEADER_ */ line, that is up to the end.

b) .c files should contain relatively small sets of tightly related
functions. Their declarations go in a .h file with the same name, to
be included by all the other .c files that use those functions.

c) Type definitions (typedef, struct) must go in .h files, as soon as they are
used by more than one .c file. Defined type names should visually suggest
that they are not base types (like int or float). An often used convention
is to write their names using caps:
typedef int* Foo;
typedef float GlobeTrotter[3];
typedef struct _BarfBeer {
int a,b;
struct _BarfBeer* next; /* For a linked list */
} BarfBeer;

d) Preprocessor directives defining constants and macros go in .h files as
soon as the constants and macros are used by more than one function (it
annoys me to have to fish through a .c file for the definition of a macro,
and I'd much rather see it in a short .h). Constants used by just one
function are #define-d right before it in the .c file, and #undef-ined
immediately after. Defined constant names should be ALL_IN_CAPS

e) Global variables are declared with the "extern" storage qualifier in .h
files, which are included by all the .c files that need those
globals. There must be only ONE definition for each global variable (i.e. a
declaration without "extern"), and it MUST be in a .c file. If you put it
in a header, you get a multiple definition error as soon as that header is
included by more than one .c file. It doesn't hurt, and greatly helps
code readability, to redeclare at the beginning of a function all extern
variables used by that funciton (of course using the "extern" storage
qualifier).

f) If at all possible, do not include header files in header files, and let
only the .c guys do all the #include-ing.
A non ``flat'', hyerarchical inclusion structure is more suited to C++.

P.S. A "storage qualifier" is a keyword specifying the type of storage needed
for a certain variable. Example of C storage qualifiers are: static,

extern, register, volatile.

#######################################################

Extern Variables and Functions

Because a global variable may be defined in one file and referred to in other files, some means of telling the compiler that the variable is defined elsewhere may be needed. Otherwise, the compiler may object to the variable as undefined. This is facilitated by an extern declaration. For example, the declaration

extern int size; // variable declaration

informs the compiler that size is actually defined somewhere (may be later in this file or in another file). This is called a variable declaration (not definition) because it does not lead to any storage being allocated for size.

It is a poor programming practice to include an initializer for an extern variable, since this causes it to become a variable definition and have storage allocated for it:

extern int size = 10; // no longer a declaration!

If there is another definition for size elsewhere in the program, it will eventually clash with this one.

Function prototypes may also be declared as extern, but this has no effect when a prototype appears at the global scope. It is more useful for declaring function prototypes inside a function. For example:

double Tangent (double angle)

{

extern double sin(double); // defined elsewhere

extern double cos(double); // defined elsewhere

return sin(angle) / cos(angle);

}

The best place for extern declarations is usually in header files so that they can be easily included and shared by source files.



No comments:

My photo
London, United Kingdom
twitter.com/zhengxin

Facebook & Twitter