Why is it not good practice to include source files into other source files? Better way is to include header files instead. What are the benefits from such approach and what are the drawbacks from the vice versa? Excuse my bad English.
Why we should not include source files in C
2.6k views Asked by Radoslaw Krasimirow AtThere are 5 answers
The c source files must have definitions, for example if you have a function int add(int, int)
that adds two numbers, then a definition of it would look like
int add(int x, int y)
{
return x + y;
}
a header file, contains a prototype that helps the compiler call this function when it's called in your code, it tells the compiler how to create the stack frame for the function, how many parameters and their types, and the return type.
If you include a c source file containing the code sample from above, then two definitions of the function add()
will be needed, which is not possible.
Instead you add the prototype to a header file, like this
int add(int x, int y);
and then include the header file, this way there will be a single definition for the add()
function.
You might be asking yourself then, how will the function work if I use it in another c source file without providing a definition?
The answer is that the function definition is only required when the compiler links all the object files into the final binary.
As mentioned before, the main argument against including C files into C files, is the high risk of multiple definition errors. And since it is a very seldom used technique, it causes unexpected side effects for code maintainers.
Of course, in very special cases, including a C-File might be the lesser of two evils. For example, if you want to write unit tests for static c functions, you might include the C file into the file with the unit test.
see also: How to test a static function
Another unusual but valid use is separating class or function templates from their definition (C++): https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl
Any file #include
d is compiled as if its text does replace the corresponding #include
directive by the preprocessor. Although not exacty fitting, here you might find further information about this here. Note the preprocessor-guard.
The actual question is, what you should not put into such a header. That would be everythink which makes a names with external linkage be defined in more than one compilation unit/module. You also should not put objects here, which are used in only one such module, and/or shall be hidden from other modules.
That would include functions in general: the header only provides the declaration, the definition will be in a single module. An exception are inline
functions, which actually have to be defined in the header.
For data structures, most times, the same applies as for functions. However, there might be exceptions for static
structures which have to be provided by all modules. Another exception might be automatically generated files, e.g. tables which are only used by a single module. These should also be #include
d, but declared static
. Normally, one would use a different extension for such files, e.g. .inc
instead of .h
.
To a preprocessor, the extension of a file really doesn't matter. You could put code into a file with a "JPG" extension and you could still #include it without error provided that the code is legit.
One of the reasons why, conventionally, it's considered bad practice to #include
files with a source file extension is from a basic build/make perspective. Imagine you are porting a large-scale project to a new, cross-platform build system (say, 50 million lines of code).
You now have to specify which files are to be built as separate compilation units (object files) to be compiled separately and linked to form the resulting binary. If your codebase has a habit of using the preprocessor to include files with a source file extension, then you have no idea just looking at the file extensions which files are to be build as separate compilation units and which files are actually just meant to be included by the preprocessor. So then you might face a spam of errors just trying to build all the source files as separate compilation units as a sane person would, and may have to debug your build process using a fine-tooth comb while inspecting all your code and trying to figure out which file is meant for what.
At a higher level, beyond file extensions, if you actually define things in source files and include them with the preprocessor, then you risk redundant linker definitions of the same symbols, tricky link-time (and possibly compile-time) errors. Moreover, this can exhibit a general breakdown in thinking between the separation of interface/declaration (headers) and implementation/definition (sources).
There are exceptions like unity builds which do this as a build-time optimization and may be somewhat acceptable with careful coding standards and with real, measured benefits to the practice, but in general, including source files can be really confusing and a sign that the developer doesn't really understand the point of separating declarations from definitions or the confusion this can cause when trying to establish a build system.
Source files contain definitions. These can cause multiple definition errors and thus generally should not be included in other source files. Even if you avoid multiple definition error by compiling only the files which includes other source file, code can become unmanageable.
In header files, you just introduce some symbols to compiler and inform their types. This allows you to separate the interface with the implementation.
For example:
file a.c
file b.c
When you compile
a.c
andb.c
and link them you will getmultiple definition
linker error.If one plans to include multiple source files into just one file and compile that one file, it will introduce a lot of pollution (macros, static functions etc) which is something not very manageable for readers and for the compilers.
p.s. When I say generally, I mean sometimes including source code may be useful. But in such cases to avoid the confusions to readers, I would prefer to rename the file suffix to some thing other than
.c
, may be.inc
or similar.