Can anyone who's worked on a well-written, large C project comment on the best way to structure headers and include files?
I don't like including stdio.h and stdint.h in every .c and .h file, but it seems like the "standard" way to compile with Makefiles is to compile each .h and .c pair as an object then link them together at the end. So you need the header files to use includes and such in every file or the compiler barfs.
Every time I try to structure a C project I feel like I'm doing it wrong and that there must be an elegant solution that I'm missing.
Then, in each project file, at the top, just include:
#include "myproject.h"
There are all sorts of niggling little reasons why you aren't supposed to do this, just like there are niggling little reasons why you're supposed to cast functions that return values to (void) when you don't use their values. Ignore them and get on with your life.
I'm not a C/C++ expert but I can highly recommend the book "Large-Scale C++ Software Design" by John Lakos. It gives an extensive discussion on how to structure programs and addresses the issue above in detail, in addition to many others. I'd be very interested to hear the opinions of any HN C++ experts on the principles espoused in this book (assuming a few have read it!).
I'm not positive what you mean "you need the header files to use includes". header files should generally not include other files. C files include headers. If the headers have a dependency that can't be resolved by a forward declaration, it's often best to make the C file include the headers in the right order.
headers including headers leads to messes where people just include a random selection of headers and hope that pulls in everything they want. It's very annoying to untangle years later.
If I have data types defined as function arguments in header files, I need to include the headers for those data types. There's apparently no way around this, and it does result in messy dependencies.
I'm just curious if large projects have a systematic way of dealing with the dependency tree created by having fine-grained compilation units.
The simplest way to write a C program is as a single file. Then you have none of these problems, other than it's very difficult to navigate through the code as nothing is separated by functional area, and you have to recompile the whole program for every small change.
Next in the complexity chain, you could break apart the single C file into multiple .c files and include them in the right order, and not use header files at all. However, that still requires that you recompile the whole program for every change.
So now we're down to compiling every separate .h/.c entity as its own object file to speed up compilation, but structuring these files so that they compile and include everything in the correct order becomes a much bigger problem.
There are other data types besides pointers to structs. Specifically, stdint.h data types are a good example (on linux).
int
myfunction(uint64_t arg0);
When writing cross-platform code, there are typically a lot of these kinds of things going on.
Also, I typically include data type definitions in header files if they're used in more than one context. If I define a new data type in a header file that depends on other data types, I have to include those headers in the header file.
Let's say I'm writing an encoder/decoder for a protocol and I want to separate the encode functions from the decode functions. Maybe they're in separate programs. They'll both use the same data structures for the most part.
It it's a network protocol the layers of dependencies can get pretty deep.
This isn't a problem with a one-liner solution, it's "how do you structure a huge project to reduce weird dependency issues when compiling while keeping the code readable?"
(Apoligies, HN is mangling my comment when I try to include C style comments, so I'm using C99/C++ style ones)
You should include whatever .h files you need in every file that uses them (things like stdlib and stdio for sure when you're using them). This makes it so you have fewer interfile dependencies which keeps your compile times down. So you only include other .h files in .h files if you need the types in them (or put a blank prototype in for it, aka struct foo;). Otherwise you include .h files only in .c files.
Feels hairy, yes, but that's the correct way to do it. By doing it creative ways, you cause great pain down the road (especially compilation times, and hunting for simple bugs). If you're only using one or two functions, sometimes people just prototype the function:
// some people just would stick this prototype in their .c
//file they're writing if they need nothing else in math.h
double sqrt(double x);
I think you're missing something about C there though the way you talk about compiling things.
h files aren't really compiled at all. They are substituted into files where they're #included. You can actually write your entire project without them if you really want to (not suggested, but you can) by just putting prototypes for the functions you call at the top of the c files that need to copy them. That's actually why its important to put guard include statements at the beginning and end of h files to make sure they aren't included more than once. A properly written h file shouldn't cause compiler errors when you do this to it:
#include "foo.h"
#include "foo.h"
#include "foo.h"
#include "foo.h"
#include "foo.h"`
The way you make it proper is you do this the top:
#ifndef __FOO_H__
#define __FOO_H__
and this at the bottom
#endif // __FOO_H__
Many IDEs do this automatically and vim and emacs both have plugins you can get to do this. Robert Pike's piece is from 1989, when computers ran much slower. The lexical analysis time from this is not worth talking about anymore.
Structure:
Generally C is structured with a main .c file which has a few functions related to start up in it and includes of header files in other modules which are needed. Each of the .c files includes the header files it needs, from either internal project modules, or from system level header files (aka <thingsInAlligatorBrackets.h>). Generally each .c file is a module, although in Object Oriented C, you may find modules built up of several different c files with each "class family" in a single .c files. Modules are purely conceptual in C.
Some C people get macro happy. Do no do this. It is unclear to the rest of the C developing universe. Modern C compilers can do inline substitution quite well, often giving better performance than your macros. When people get macro happy, they end up making a file like "GlobalMacros.h" or even if they don't, they just have a lot of constants and end up with "LotsOfConstants.h" included in everything.
Resist the urge to put your .h includes in there but NOT in the files that actually depend on it. If you do that you break code if that .h file is ever taken out.
As far as final compilation goes: Every C file is compiled into an object file then linked using the linker. Alternatively, groups of .c files are compiled into .o files then linked into .a files (static libraries). These libraries are then linked in during the final compilation step.
Your header guard is bad (but not horribly so). Identifiers with two underscores at the beginning are reserved in the C90 standard (as are identifiers with one underscore followed by an upper case letter).
Your comments are also only valid in C99. Unless you have a really good reason, then you should use the block style comments (and I can't think of any good reason). (oh, maybe I should have read your very first sentence... yes that seems like a good reason :-)
>Your header guard is bad (but not horribly so). Identifiers with two underscores at the beginning are reserved in the C90 standard (as are identifiers with one underscore followed by an upper case letter).
That's not actually the header guard that pops out when I put them in (as I do so automatically using vim code similar to that described below), however you are correct that you have a chance to clash with compiler dependent info macros (such as __LINE__) if you use a double underscore before a header guard.
I'm going to see if I can edit the post for those who don't see this exchange below.
I don't like including stdio.h and stdint.h in every .c and .h file, but it seems like the "standard" way to compile with Makefiles is to compile each .h and .c pair as an object then link them together at the end. So you need the header files to use includes and such in every file or the compiler barfs.
Every time I try to structure a C project I feel like I'm doing it wrong and that there must be an elegant solution that I'm missing.