So I’m still getting used to modular programming, and want to make sure I’m adhering to best practices. If I have the two module header files below, will the the headers #included by each file (for example “mpi.h”) be included multiple times? Is there a proper way to account for this?
Also, my module headers typically look like these examples, so any other criticism/pointers would be helpful.
/* foo.h */
#ifndef FOO_H
#define FOO_H
#include <stdlib.h>
#include "mpi.h"
void foo();
#endif
and
/* bar.h */
#ifndef BAR_H
#define BAR_H
#include <stdlib.h>
#include "mpi.h"
void bar();
#endif
And use the sample program:
/* ExampleClient.c */
#include <stdlib.h>
#include <stdio.h>
#include "mpi.h"
#include "foo.h"
#include "bar.h"
void main(int argc, char *argv[]) {
foo();
MPI_Func();
bar();
exit(0)
}
What do you mean by ‘include’? The preprocessor statement
#include filecopies the contents offileand replaces the statement with these contents. This happens no matterIf by ‘include’ you mean “the statements and symbols in these files will be parsed multiple times causing warnings and errors”, then no, the include guards will prevent that.
If by ‘include’ you mean “some part of compiler will read some part of these files”, then yes, they’ll be included multiple times. The preprocessor will read the second inclusion of the file and replace it with a blank line because of the include guards, which incurs a tiny overhead (the file is already in memory). Modern compilers (GCC, not sure about others) will probably be optimized to avoid this, however, and note that the file has include guards on the first pass and simply discard future inclusions, removing the overhead – Don’t worry about speed here, clarity and modularity are more important. Compilation is a time-consuming process, for sure, but
#includeis the least of your worries.To better understand include guards, consider the following code sample:
After (the first pass of) preprocessing, what will
GUARDEDbe defined to? The preprocessor statement#ifndefor its equivalent,#if !defined()will returnfalseif their argument is indeed defined. Therefore, we can conclude that the second#ifndefwill return false, so only the first definition of GUARDED will remain after the first pass of the preprocessor. Any instance ofGUARDEDremaining in the program will be replaced by 1 on the next pass.In your example, you’ve got something slightly (but not much) more complicated. Expanding all the
#includestatements in ExampleClient.c will result in the following source: (Note: I indented it, but that’s not normal style for headers and the preprocessor won’t do it. I just wanted to make it more readable)Go through that code and note when various definitions are performed. The result is:
With respect to your request for other criticism/pointers, why are you #including stdlib.h and mpi.h in all your headers? I understand that this is a stripped down example, but in general, header files should only include files necessary for the declaration of their contents. If you use a function from stdlib or call MPI_func() in foo.c or bar.c, but the function declarations are simply
void foo(void), you shouldn’t include these files in the header function. For example, consider the following module:foo.h:
foo.c:
In this example, the implementation of
foo()requires stuff from stdlib and mpi, but the definition does not. If foo() returned or required asize_tvalue (typedef’ed in stdlib), you’d need to #include stdlib in the .h file.