Is the double inclusion problem (solved by include guards & #pragma once) simply a preprocessor imperfection, which its causes being sorts of historical?
There is no double inclusion problem; rather, there is a multiple definition problem. Though it depends on the content of the header, without multiple-inclusion guards it would be relatively common for multiple inclusion of the same header to effect disallowed multiple definition of one or more identifiers. The convention of using multiple-inclusion guards was developed to protect against that, and it has become routine practice even for headers that don't actually present a multiple-inclusion problem.
Functions and objects should not be defined in headers, though declaring these in headers is one of headers' main use cases. It's fairly easy to avoid that aspect of the issue. It used to be the case, however, that it was an technically an error to redeclare the same typedef
name, even with an identical definition, and some pre-standardization compilers rejected even identical redefinition of macros. Neither of these is an issue in modern C (C2011).
What does remain an issue is redefinition of struct
, union
, and enum
types -- this is still disallowed, even if the redefinition changes nothing. Secondarily, it is not uncommon for the meaning of the contents of a header file to be dependent on symbols that may be defined elsewhere in the translation unit. If such a header's contents are included more than once in the TU then it is possible for them to have different meaning on the two inclusions. That can lead to incompatible multiple declarations.
The point to be understood is that whether multiple-inclusion of the contents of a given header is wrong is a function of those contents.
Additionally, I already described how conditional compilation makes it possible for the meaning of a header to differ depending on where it is included relative to the rest of the translation unit. Although this can contribute to producing disallowed incompatible declarations or multiple definitions, it can also produce perfectly valid code.
TL;DR: multiple inclusion of header files is not inherently wrong, and can serve a useful purpose. It is therefore not a flaw that the C preprocessor does not automatically protect against multiple inclusion. It is reasonable for the responsibility to rest on the contents of each header to exhibit suitable behavior when that header is included more than once into the same translation unit.