7 ms·
Notes on Programming in C (1989)
- Peaker 12y ago> Simple rule: include files should never include include files. If instead they state (in comments or implicitly) what files they need to have included first, the problem of deciding which files to include is pushed to the user (programmer) but in a way that's easy to handle and that, by construction, avoids multiple inclusions. Multiple inclusions are a bane of systems programming. It's not rare to have files included five or more times to compile a single C source file. The Unix /usr/include/sys stuff is terrible this way. > There's a little dance involving #ifdef's that can prevent a file being read twice, but it's usually done wrong in practice - the #ifdef's are in the file itself, not the file that includes it. The result is often thousands of needless lines of code passing through the lexical analyzer, which is (in good compilers) the most expensive phase. > Just follow the simple rule. This is a ridiculous stance, in my opinion. Part of the appeal of libraries is abstracting away implementation details. I want to be able to have foo.h #include a hash table, or a binary search tree, depending on #ifdef's or its internal implementation. The user of foo.h shouldn't even have to know about it. Compilers can easily memoize the position and parsed content of preprocessor directives, and do a very quick skip of an #include file, as a much better way to resolve the problem. Rob's resolution is an instance of worse-is-better: Instead of slightly complicating the lower-level tool (in this case, the preprocessor), let's complicate all user code in existence.
- krig 12y ago> Compilers can easily memoize the position and parsed content of preprocessor directives, and do a very quick skip of an #include file, as a much better way to resolve the problem. There are reasons why actual C/C++ compilers don't do this. Additionally, recursive includes is a major reason why C++ compilers are slow. For more on this, see http://clang.llvm.org/docs/Modules.html http://clang.llvm.org/docs/Modules.html
- clarry 12y ago> There are reasons why actual C/C++ compilers don't do this. http://gcc.gnu.org/onlinedocs/cpp/Once-Only-Headers.html#Once-Only-Headers http://gcc.gnu.org/onlinedocs/cpp/Once-Only-Headers.html#Onc... This construct is commonly known as a wrapper #ifndef. When the header is included again, the conditional will be false, because FILE_FOO_SEEN is defined. The preprocessor will skip over the entire contents of the file, and the compiler will not see it twice. CPP optimizes even further. It remembers when a header file has a wrapper ‘#ifndef’. If a subsequent ‘#include’ specifies that header, and the macro in the ‘#ifndef’ is still defined, it does not bother to rescan the file at all.
- pjmlp 12y agoWhich breaks #includes written to be included multiple times and provide different outcomes based on defined macros. C based toolchains are just broken.
- clarry 12y agoIf a file is intended to be read multiple times with different results, then obviously you don't want to prevent it being read twice. However, we were talking about preventing the hit from reading a file twice unnecessarily. The quoted text says it doesn't bother rescanning if the macro is still defined. How do you get a different outcome from scanning the same file with the same define? Can you provide an example where this breaks things?
- Nursie 12y agoIt can also be dangerous. Take (for instance) a header file that relies on a specific integer size. Using this scheme one could include a different/wrong typedef and get yourself into trouble.
- rootbear 12y agoIt seems to me that all this fuss over multiple includes and include guards would be solved by something like a #stop directive: #ifdef STDIO_H #stop #endif #define STDIO_H which would cause the preprocessor to skip the rest of the file. How would this not be a better solution? What am I missing?
- RegW 12y agoIt would be faster, but this would still cause the compiler to open and start parsing the file unnecessarily (possibly multiple times from multiple included files). Another approach would be to have a pragma that means: "just don't bother including this file again if you see it", and have the compiler remember it. Perhaps there is - its a long time since I programmed in C.
- clarry 12y agoIf the compiler can remember there's a pragma like that, then it can also remember there's an include guard wrapping the file.
- tbirdz 12y agoThere is #pragma once It's not standard, but it is supported by many compilers.
- rootbear 12y agoGood point. Opening a file has non-trivial cost, so perhaps the added cost of burdening the compiler with avoiding needless includes is worth it. As a long time C hacker I hate to say this, but I guess the whole include mechanism is a bad design or, perhaps, a design that didn't scale well.
- icantthinkofone 12y ago> This is a ridiculous stance, in my opinion. I always find it humorous to find posters here and reddit and other boards say these things and I believe they would make the same statement about the Ten Commandments from God.
- Peaker 12y agoYes, at least a few of the Ten Commandments from God are ridiculous. What's your point?
- icantthinkofone 12y agoMy point is most people could learn a lot from him and aren't in the position to make claims against him.
- Peaker 12y agoThere are plenty of things Rob says I strongly disagree with, and even if you're very impressed with his work, your comment is still a very silly appeal to authority.
- icantthinkofone 12y agoIn this life, you must find someone to believe in. Dismissing this as a "silly appeal to authority" is even sillier.
- yohanatan 12y agoMaybe you need someone to believe in. Stronger minds decide for themselves (interestingly enough which is the advice given by Rob in the intro of this article).
- icantthinkofone 12y agoA strong mind listens to others who may be smarter than themselves and have more experience and doesn't worry about who the stronger mind is.
- orbifold 12y agoHave you looked at the code for plan9? It follows this principle and I think it works fairly well. Its much nicer than wading through a large number of preprocessor directives.
- pmarin 12y agoAll Plan9 C programs are written in this way. Paradoxically the number of includes in your code is minimal compared with the usual POSIX aproach. All C programs begin with two includes: u.h and libc.h and usually libio.h and it is often all you need for the startdard library. The man pages of the libraries describe the libraries you need to include. Example: Plan9 cat: http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cat.c http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/cat.c GNU cat: http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f=src/chcon.c;h=32d4b0f8b7aa98830ec6404c83801c5115f29854;hb=HEAD http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f... Plan9 du: http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/du.c http://plan9.bell-labs.com/sources/plan9/sys/src/cmd/du.c GNU du: http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f=src/du.c;h=890edb64c31a41fdd0bf9a3f24607369c436928f;hb=HEAD http://git.savannah.gnu.org/gitweb/?p=coreutils.git;a=blob;f...
- Peaker 12y agoSo basically the #include granularity is extremely coarse, because otherwise it is unmanageable. I think it is nicer to have quite specific #include's: #include <stdbool.h> #include <stdarg.h> #include "hash_table.h" Because it: * Gives a list of dependencies at a glance * Along with preprocessor optimizations to avoid reparsing protected headers, it can actually parse fewer headers in each compilation (reversing the performance argument by Rob) * Can aid with greps for where modules are used The comparison is really a strawman -- and the length of the GNU one and its many dependencies relate more to feature creep than to the #include issue here. At least with GNU's #includes, I can see that it depends on the various stuff it needs for its feature creep.
- ufo 12y agoWhy do all the P9 files use `void main` instead of `int main`?
- pmarin 12y agoYes. Instead of returning an integer you use the function exits [1] and the value appears in the shell variable status. [1] http://plan9.bell-labs.com/magic/man2html/2/exits http://plan9.bell-labs.com/magic/man2html/2/exits This is an introduction to programming in Plan9 C: http://doc.cat-v.org/plan_9/programming/c_programming_in_plan_9 http://doc.cat-v.org/plan_9/programming/c_programming_in_pla...
- matthewmacleod 12y agoObjective-C's #import directive basically pushes this work to the compiler, and is quite effective.
- tbirdz 12y agoTodays compilers may be fast enough so that using #ifndef that way would not make much of an impact, but back in 1989 when this article was written, presumably the time spent was much greater.
- cdellin 12y ago> This is a ridiculous stance, in my opinion. > Part of the appeal of libraries is abstracting away implementation details. I want to be able to have foo.h #include a hash table, or a binary search tree, depending on #ifdef's or its internal implementation. The user of foo.h shouldn't even have to know about it. I agree with your premise (re: abstraction of implementation details), but I think you've applied it incorrectly to reach the wrong conclusion. If the implementation of foo (contained in foo.c) uses a hash table or binary search tree, then foo.c may of course include whatever it likes. On the other hand, foo.h is (by definition) the interface to foo; any headers the interface requires (e.g. for function argument types) ought to be explicitly understood by the user of the library.
- Peaker 12y agoIt depends on whether you delegate all memory allocations to the C file. In the C code I work on, dynamic memory allocations are considered expensive and problematic (fragmentation, runtime errors) so we prefer to move the responsibility for allocation as far up the call-stack as possible, where it is often possible to minimize or coalesce dynamic allocations. To do this, the interface files must expose the correct sizeof() so that the caller can allocate. We do this by splitting the header file into foo.h and foo_private.h (foo.h #includes foo_private.h). This separation is not for enforcement, but for API clarity and convention (if it's in a private.h, you have no business assuming anything about it). This forces us to rebuild when the sizes of private structures change (which can be a significant downside) but allows us to avoid dynamic allocations, their costs and their many potential error paths. In this approach, foo_private.h may very well #include a hash_table or whatever header which should not* be exposed to the #includer.
- gluczywo 12y agoThe ugly "includes hack" seems ridiculous today, but in the 90's such techniques were the only way to compile large C/C++ systems in a reasonable time. Messing with includes and reducing file dependencies while less important nowadays are underappreciated elements of software design (called physical design as opposed to logical design). For better historical context see: John Lakos - "Large scale C++ software design" (1996)
- ttty 12y ago" Procedure names should reflect what they do; function names should reflect what they return." - Never thought about naming this way, but is what I do (:
- Zolomon 12y agoNice list of books they have in their library: http://www.lysator.liu.se/lib/boklista.txt http://www.lysator.liu.se/lib/boklista.txt
- eoi 12y agoIt's interesting to see how some of these opinions are reflected in Go, because Rob Pike helped design Go about 20 years after writing this essay: Typography: Automatic formatting is part of the standard Go tooling and culture. Function pointers / Protocols: This section seems to be decsribing a technique similar to Go's interfaces. Include Files: Rob Pike talked about this in his presentation at SPLASH [1,8:00-12:30]. He mentioned that Plan 9 had fast compile times in part due to following this discipline. He contrasted that with one of Google's C++ code bases in which the compiler had to read 2000 bytes for every byte of source. That was largely because of includes, and was a problem for compile times. A little later he talks about Go's method of importing dependencies, and presents them as a big improvement to the situation. [1]http://www.infoq.com/presentations/Go-Google http://www.infoq.com/presentations/Go-Google