7

Going through the K&R ansi C programming language book (second version), on page 82 an example is given for a programming files/folders layout.

Copyright K&R - C programming language - Ansi C second edition

What I don't understand is, while calc.h gets included in main (use of functions), getop.c (definition of getop) and stack.c (definition of push and pop), it does not get included into getch.c, even though getch and ungetch are defined there.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
hewi
  • 1,274
  • 3
  • 17
  • 32
  • 2
    that puzzles me too :-) – Peter Miehle Dec 01 '15 at 08:51
  • 1
    As you wrote. Functions in getch.c are defined, but not used. Then you have not to include the header as far as you don't use function defined in external files. – LPs Dec 01 '15 at 08:52
  • yeah, but why is calc.h included in getop and stack? – hewi Dec 01 '15 at 08:54
  • Probably because of the use `getch` and `ungetch` functions. Or maybe `getop` uses `push` and `pop`.... – LPs Dec 01 '15 at 08:55
  • 5
    Also the signature of `getop` is different ☺ – Alex Lop. Dec 01 '15 at 08:56
  • @LPs but it would be advisable to include calc.h in getch.c: if you change the implementation to `int getch(int arg)` none of the using modules are aware of that change and are compiled syntactically correct but semantically wrong. – Peter Miehle Dec 01 '15 at 08:57
  • @ Alex Lop: very well spotted! – hewi Dec 01 '15 at 09:00
  • @PeterMiehle Compiler will worn you about such an error. He look into header to understand how the function is defined, and after look into sources to find the correct definition and implementation. – LPs Dec 01 '15 at 09:00
  • 2
    `is does not get included into getch.c, even though getch and ungetch are defined there`, in old C (pre-ANSI and C89) you do not have to declare a function prototype. – David Ranieri Dec 01 '15 at 09:01
  • @LPs No, that should create a linker error, not a compile error. A linker error is obviously undesirable because it happens in a later stage of the build process. – VLL Dec 01 '15 at 09:03
  • @LPs but only, if you include the declaration. in the OPs example the compiler will throw an error for getopt because of mismatch, but will not, if you have a mismatch for getch. – Peter Miehle Dec 01 '15 at 09:03
  • @Ville-ValtteriTiittanen you are wrong. The linker has nothing to do with definitions and declarations, only with namespaces (so if you leeve out a function, the linker will throw an error) – Peter Miehle Dec 01 '15 at 09:06
  • 1
    This is complete nonsense. It is really hard to figure out what was going on in the heads of K&R when they wrote this. This is a school book example of spaghetti program design, where completely unrelated functions are dumped everywhere, with a common spammy header. What does "calc" got to do with input functions? A proper program design would separate stack functionality, input and the actual algorithm into three separate pairs of .h and .c. There is nothing to learn from this example, there's nothing you should study or understand. Don't write programs like this! – Lundin Dec 01 '15 at 09:07
  • In particular "...the practical reality that it is harder to maintain more header files. Up to some moderate program size, it is probably best to have one header file that contains everything that is to be shared between any two parts of the program; that is the decision we made here." is pure BS. Everyone stop reading this book already! It is _harmful reading_ and will make you _worse_ at programming! – Lundin Dec 01 '15 at 09:12
  • @PeterMiehle (Assume you change definition of some function in `getch.c`.) All files that use functions that are declared in `calc.h` include the declaration. This means compiling these files will succeed. Compiling `getch.c` will succeed as well. After that, linker will look for function that was defined according to declaration in `calc.h`. This will fail with "undefined external reference" error. – VLL Dec 01 '15 at 09:12
  • @Ville-ValtteriTiittanen you want me to tell that the linker looks into headerfiles? AND: the function is properly defined: `func.c: int foo(int b) {} main.c: double foo(char x, char*y); main() {double z = foo('x',"X");}` will compile an link nicely, but crashes with UB. – Peter Miehle Dec 01 '15 at 12:29

3 Answers3

9

Although it's a good idea to include the header file it's not required as getch.c doesn't actually use the function declared in calc.h, it could even get by if it only used those already defined in getch.c.

The reason it's a good idea to include the header file anyway is because it would provide some safety if you use modern style prototypes and definitions. The compiler should namely complain if for example getop isn't defined in getop.c with the same signature as in calc.h.

ElderBug
  • 5,926
  • 16
  • 25
skyking
  • 13,817
  • 1
  • 35
  • 57
  • Note that the only reason why it is a good idea to include that spammy header file is because you'll get the function prototypes in scope. The proper solution however, is to write a _separate_ header file getch.h where those function declarations should be placed. getch.c should then _only_ include getch.h and not some non-related nonsense header. – Lundin Dec 01 '15 at 09:19
  • 2
    @Lundin That is only an opinion. Fact is that the function names in that header file have external *linkage*. That is to say, they are global anyway. So there is no harm in making them all known at compile time also! And may actually be beneficial: duplicate incompatible declarations can be spotted at compile time. If `graphics.c` defines `draw` and so does `lottery.c`, it might go undetected until link time (and perhaps not even then) if there is no translation unit which includes both `lottery.h` and `graphics.h`. If both `draw` functions are declared in `common.h`, that will be caught. – Kaz Jan 06 '16 at 22:37
  • @Lundin One valid perspective is that C header files are basically a nuisance. That is why the [makeheaders](http://www.hwaci.com/sw/mkhdr/) tool exists: you just write code and it generates/updates the headers from it as part of the build process. The headers it generates actually duplicate material; `foo.c` includes `foo.h` only, and `foo.h` contains everything that is required by `foo.c`. – Kaz Jan 06 '16 at 22:42
  • Of course, there are serious disadvantages in using a common header. It is harder to yank a source file and re-use it elsewhere. (Then again, use libraries for re-use instead scavenging pieces of programs! Libraries can internally use the "one header" approach just fine.) It does encourage bad modularity: what are the dependencies? You don't see them. You can write modules that aren't cohesive or loosely coupled quite easily. Everything sees the global space, and so any function can live anywhere. However, this problem *is* solved by programmers using languages that have no headers! – Kaz Jan 06 '16 at 22:51
  • @Kaz My comment was mainly concerning proper program design rather than linking. There are de facto industry standard of how you should design C programs: the opinion of the vast majority of C programmers. The K&R book does not address proper program design. – Lundin Jan 07 '16 at 08:01
1

calc.h contains the declaration of getch() and ungetch(). It is included by files that want to use these functions (and, therefore, need their signature).

getch.c, instead, contains the definition of getch() and ungetch(). Therefore, there is no need of including their declaration (which is implicitly defined in the definition).

Claudio
  • 10,614
  • 4
  • 31
  • 71
  • 2
    the declaration must match the definition, so it is best practise to include the declarating header file into the defining source file. – Peter Miehle Dec 01 '15 at 08:58
  • 4
    " there is no need".. You're *technically* correct (the best kind, I suppose), however it's a very bad idea to do this in practice. You want the declaration visible at the point of definition so that the compiler can diagnose a mismatch. – M.M Dec 01 '15 at 08:59
0

The omission you have so aptly discovered can be a source of a real problem. In order to benefit fully from C's static type checking across a multi-translation-unit program (which is almost anything nontrivial), we must ensure that the site which defines an external name (such as a function) as well as all the sites which refer to the name, have the same declaration in scope, ideally from a single source: one header file where that name is declared.

If the definition doesn't have the declaration in scope, then it is possible to change the definition so that it no longer matches the declaration. The program will still translate and link, resulting in undefined behavior when the function is called or the object is used.

If you use the GNU compiler, you can guard against this problem using -Wmissing-prototypes. Straight from the gcc manual page:

       -Wmissing-prototypes (C and Objective-C only)
           Warn if a global function is defined without a previous prototype
           declaration.  This warning is issued even if the definition itself
           provides a prototype.  The aim is to detect global functions that
           fail to be declared in header files.

Without diagnosis, this kind of thing, such as forgetting a header file, can happen to the best of us.

One possible reason why the header was forgotten is that the example project uses the "one big common header" convention. The "one big common header" approach lets the programmer forget all about headers. Everything just sees everything else and the #include "calc.h" which makes it work is just a tiny footnote that can get swallowed up in the amnesia. :)

The other aspect is that the authors had spent a lot of time programming in pre-ANSI "Classic" C without prototype declarations. In Classic C, header files are mainly for common type declarations and macros. The habit is that if a source file doesn't need some type or macros that are defined in some header, then it doesn't need to include that header. A resurgence of that habit could be what is going on here.

Kaz
  • 55,781
  • 9
  • 100
  • 149