Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Quotes vs. angle brackets in #includes

1 view
Skip to first unread message

Steve Summit

unread,
Jun 5, 1997, 3:00:00 AM6/5/97
to John Winters

-----BEGIN PGP SIGNED MESSAGE-----

[posted and mailed, and crossposted to comp.unix.programmer,
although the issue is *not* unique to Unix]

Several weeks ago, in <5kve70$t...@polo.demon.co.uk>, John Winters
<jo...@polo.demon.co.uk> wrote:
> It's not often I disagree with Steve but....
>
>In article <E9tIv...@eskimo.com>, Steve Summit <s...@eskimo.com> wrote:
>> They each have their uses. For Standard headers, you always
>> want to use angle brackets. For your own headers pertaining
>> to a single, self-contained program, you almost always want
>> to use double quotes. For third-party headers, or your own
>> headers describing libraries which you use in multiple or
>> large projects, there are viable (and acceptable) strategies
>> involving either angle brackets or double quotes (along with
>> whatever mechanisms your compiler provides for augmenting the
>> header file search path(s)).
>
> I find it hard to conceive of a justification for using angle
> brackets for your own header files...

Well, there was one described in the earlier article of mine
(part of which you quote below, and which I'll say more about).
But that involved a somewhat obscure situation, and I think I
might even be prepared to justify the use of angle brackets for
other than standard headers on more prosaic grounds, such as that
they simply look better or send a different message. Double
quotes say "These are local headers." Angle brackets say,
"These are system headers." Where do library and third-party
headers fall? Squarely in the middle. Two kinds of header
inclusion aren't really enough, but they're all C gives us.
If you've got a shared or third-party header to #include, and you
don't want to make it look local, it's very tempting to put it in
angle brackets (perhaps with a blank line separating it from the
*really* standard headers up above). At least, *I've* been
tempted to do so; in fact that's the way I tend to do it.

Now, it's true that you can certainly decide to use double quotes
for all library headers, and it's true that if you focus on the
Standard, angle brackets don't just say "These are system
headers", they say, "These are Standard headers." So there are
definitely arguments in favor of leaning strongly towards double
quotes for all but Standard headers, but there are also (I claim)
some reasons -- which are at least decent, if not necessarily
compelling -- to lean towards angle brackets for some of your
less than purely local headers.

> If you use them, you've no guarantee that your
> code will then compile at all on another implementation, regardless of
> how portably you otherwise write it.

That's true in theory, but these arguments aren't always
black-and-white. There are certainly antecedents of "them" for
which I'd agree with you up, down, and sideways, that is, there
are certainly nonportable practices which I agree should never
be used in code that pretends to be "portable." There are other
examples of implementation-defined behavior (or, at least,
not-precisely-defined-by-the-Standard behavior) which I'm sure
you'll agree that any program can reasonably tolerate. (For
example: the Standard doesn't say exactly what \n does or where
printf's output goes; there are clearly some implementation-
defined aspects here even if they're not formally classified as
"implementation defined." Yet practically every C program calls
printf, or prints \n's, or both.) And then, there are a few
issues which (like the non-local, non-Standard headers) fall
squarely in the middle: a solution might not be 100% strictly
conforming, but there might not be any 100% strictly conforming
solution which is practicable, so one might have to settle for
a compromise.

>> On the other hand, the problem as I've stated it only comes up if
>> you use double quotes. If you use angle brackets for the central
>> project header files (in particular, for nested #inclusions of
>> central project header files), the preprocessor never starts at
>> the wrong "current" directory, but always traverses the search
>> path from the start again, and finds the one in your private
>> include directory, as you wanted, regardless.
>
> Hmmmm. There was no PGP signature so it could be a forgery I suppose.
> Steve, did you really write this paragraph?

I did; no forgery; and (just this once) I've made sure that you
don't have to doubt this article.

> Presumably you had some
> particular implementation in mind, but in general there is no
> reason to expect the behaviour which you describe.

The "particular implementation" I had in mind is none other than
Unix, along with at least half of the non-Unix compilers out
there which have followed the old Unix preprocessor's lead in
handling double quotes. The question is, if double quotes in
an #include directive mean "use the current directory", which
current directory is it? If the directory we were in when we
invoked the compiler and the directory containing the .c file are
the same, then there's little question. But what if the .c file
is not in what we would otherwise think of as "the current
directory"? Worse, what if a .c file (in the current directory)
includes a header file, which for whatever reason is not in the
current directory, and that header file contains an #include
directive using double quotes? Many preprocessors, including
the traditional Unix ones, take the position that "the current
directory" which is first searched by #include "" in that case
is *the directory containing the file containing the #include
directive*. (I know you know about this possibility, John, but
evidently you haven't had to deal with some of its even more
surprising consequences.)

Why would you want this rule at all? Why not make "the current
directory" be unilaterally *the* current directory? The answer
is that, at least some of the time, the Unix behavior is exactly
what you want. Suppose I'm trying to share some code, but in an
informal way, neither as a library nor even as a precompiled
object file; suppose that I call (in my Makefile, project
definition, or other build procedure) for some .c file in some
other directory to be compiled and the resulting object linked
into my program. (Why am I compiling it myself, instead of
using a precompiled object or library? Perhaps I need to compile
it with my own set of macro definitions to pick a certain set
of #ifdef groupings.) Suppose further that this source file
I'm trying to compile has, off in its own directory where I'm
compiling it from, its own header file which I don't otherwise
need. The Unix behavior will then "do the right thing": the
file's header, which it includes using double quotes, will be
found over in its directory.

Now, to be sure, there would have been other ways to skin this
cat. If the rule were that the current directory is always *the*
current directory, such that the preprocessor was unable to find
the remote source file's header in this case, I could have added
the remote file's directory to the list of directories to be
searched for header files. But I assume that this is the sort of
case that prompted the adoption of what I'm calling the "Unix"
preprocessor behavior, and programmers on large projects need to
understand it, because many compilers have followed Unix's lead,
but the rule does have, as I've said, some even more surprising
consequences.

Suppose I'm working with several other people on a large project.
Suppose we've got a central directory, /usr/project, containing
the project source code, and a subdirectory of central directory,
/usr/project/include, containing all the shared project source
files. (Perhaps we chose to place the header files in their own
directory because they are shared between many of the libraries
or modules which make up the project and which are distributed
among several other subdirectories of /usr/project.) Suppose
further that I have my own directory, /usr/scs/project, in which
I'm working on my own parts of the project prior to releasing
them to the central directory, and suppose that I've got a
subdirectory /usr/scs/project/include containing any project
header files which I'm modifying. Naturally, whenever I compile
part of the project, I'll arrange (using -I switches, if under
Unix) that the directories /usr/scs/project/include and
/usr/project/include be additionally searched for header files,
and in that order.

Finally, suppose that there are two files "project.h" and
"types.h" in /usr/project/include, and that "project.h" contains
the line

#include "types.h"

One day, I have occasion to make a change to types.h. I make a
copy in /usr/scs/project/include, edit it, and then arrange to
recompile (in my own directory) those parts of the project which
will be affected by the change, which will probably be nearly all
of them. When I come back from lunch, to try the recompiled
project out, I find that my changes to types.h have *not* taken
effect! Why not? Because whenever any of the project's .c files
included "project.h", they found it in /usr/project/include,
because I don't have my own local copy of project.h. But by the
rules of "the current directory is the includer's directory,"
when /usr/project/include/project.h asks for "types.h", the
first place to look is in /usr/project/include, so it finds
/usr/project/include/types.h, *not* /usr/scs/project/include/types.h.
To fix the problem (assuming I can even figure out what the heck
is going on), I need to make a copy of project.h in
/usr/scs/project/include, even though I have no intention of
modifying it, just so that all compiles will find *it* first and
so that *its* #include of "types.h" will find *my* modified copy
of types.h. (Then, I go off for a long afternoon snack break
while I recompile the entire project, again.)

Now, this may sound like such a complicated, contrived, and
obscure situation that it's not worth making implementation
strategy decisions on. I'd even be inclined to agree with you,
except that Larry Weiss knew immediately and exactly what I was
talking about, so he's obviously run into this problem, too.
(And I suspect that he and I are not alone.) When you find
yourself in this situation, you have exactly 4 choices that
I can think of:

1. Use a different compiler with a different set of rules
for resolving #include "" lines.

2. Disallow "nested #include files"; don't have project.h
do an #include on types.h at all.

3. Always check out project.h (into your own, private work
directory, that is) whenever you want to work on types.h,
and accept some wasted compilation time when you forget.

4. Have project.h use

#include <types.h>

instead, so that it always unambiguously finds the first
one in the header file search path, without tripping up
on the question of what "the current directory" is.

The last time this happened to me (for yes, as you may have
guessed, not all of these "suppose"'s have been hypothetical)
it occurred to me that *this* is a much stronger argument for a
ban on nested header files (i.e. choice #2) than the more usual
but much weaker argument of "it makes it harder to find where
things are defined," an argument which is truly convincing only
to those who are still unclear on the concept of "grep". But if
you're not willing to ban nested #include files, and if you're
not in a position to switch compilers, and if you're tired of
massive recompilations (or, worse, of searching in vain for some
other cause of a bug after your fix to a central header file
didn't seem to work), then #4 may be your best or only option,
implementation-defined though it may be.

Heretical advice? Perhaps. I'm actually not quite as much of a
Standard-thumping fundamentalist as I might sometimes sound like.
(Nor, however, am I urging everyone to use angle brackets for
local headers with wild abandon.) To be honest, I'd never
thought about this issue too carefully, but having done so, I now
place it in almost exactly the same category as I do perror().
The C Standard doesn't say that errno means anything after an
fopen() failure, but I almost always call perror() anyway,
because the resulting message is so often valid, and is so much
more useful to the user than an undifferentiated "can't open
file," that I consider calling perror() to be vastly preferable
even though, strictly speaking, it's not guaranteed to work.
(As I said, I wouldn't make quite as strong an argument in favor
of using <> with locally-defined headers, but the tradeoffs have
about the same feel to them.)

Remember, too, that "quality of implementation" is a strong but
market-driven force. How many vendors have dared to disallow
void main()? Very few, because there's so darn much code that
uses it. Similarly, I seriously doubt that many vendors (even
those who choose to build the Standard header files into the
compiler somehow such that they're not present as conventional
files at all) will make it impossible to use #include <> to bring
in a non-Standard, locally-supplied header, because there's a lot
of code that does that, too (some, as I've shown, with good or at
least decent reason).

Steve Summit
s...@eskimo.com

-----BEGIN PGP SIGNATURE-----
Version: 2.6.2

iQCSAwUBM5bgo96sm4I1rmP1AQEGNgPnUCERxygmDYr5tJFSvEJtwDvDNv+NtVAd
bIDuB4DFlLsNT8u/0CIZ7hQRufn1sQi3+nwmILMj+zgXR4Dy+av+mQywONOI4ITp
wvJV0gGmjQs3LwD4SSEKowuE+jT4vrNC67uvR4mK3iyQR3Iku4n1Y3LnfAfZp9SA
pcfoR1M=
=zhnr
-----END PGP SIGNATURE-----

0 new messages