5 ms·
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 s
by drblast 16y ago
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.
- gte910h 16y ago(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.
- loup-vaillant 16y agoTo write code, skip 2 or more characters at the beginning of the line, Markdown style. #include "foobar.h" /* no mangling */ http://news.ycombinator.com/formatdoc http://news.ycombinator.com/formatdoc
- gte910h 16y agoThanks. I was trying to do the methods explained on daring fireball about markdown, but they weren't working so I just went C99 style.
- TheNewAndy 16y agoYour 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 :-)
- gte910h 16y ago>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. http://yvan.seth.id.au/Entries/Technology/Code/Vim_Header_Guard_Trick.html http://yvan.seth.id.au/Entries/Technology/Code/Vim_Header_Gu... I was trying to simplify yet avoid the UTIL_H clash problem and made an error there.
- tedunangst 16y agoI'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.
- drblast 16y agoIf 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.
- jws 16y agoIf I have data types defined as function arguments in header files, I need to include the headers for those data types. You do not need the definition of a structure to use a pointer to it in a function prototype… struct puzzle *newPuzzle( int difficulty, struct preferences *prefs); …will compile just fine without the definition of struct puzzle. this let's you hide your opaque types inside the .c files or a private .h file.
- drblast 16y ago
- anonymousDan 16y agoI'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!).
- jbn 16y agoIt's a great book with valuable insights, i have successfully used them multiple times to decrease build times of multi-million LoC projects.
- drblast 16y agoThanks, just what I was looking for!
- rbetts 16y agoAbsolutely - a wonderful book!
- sixtofour 16y agoLogged in specifically to up vote Lakos. Will log out and log back in again to attempt a second up vote.
- signa11 16y agoif you have not looked at 'c interfaces and implementation' i would heartily recommend it. it is very, very good.
- tptacek 16y agoForget what 'gte910h says. Here's what every project I've worked on professionally has ended up doing: #ifndef MYPROJECT_H #define MYPROJECT_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/time.h> #include <sys/socket.h> #include "trie.h" #include "hash.h" #endif 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.