S
Seebs
Nevertheless, an art world aficionado
would be able to distinguish them easily (even if he couldn't
actually be sure that it was by Picasso): "THIS one is the work of a
master; but THAT one shouldn't even be in this gallery", he might
say. And the reason is simple. Picasso didn't start doing cubism (and
other abstract art forms, for all I know) until he had first mastered
what I would call "proper" painting. He learned how to paint
portraits and landscapes and still life and stuff. He learned the
"rules" of art, and learned them well. THEN he was in a position to
know how and when to break them.
Yes!
Good point.
Okay, now for the moral (which is by now, I hope, obvious). Make it a
rule NEVER to play games with #include. Use it like the books say -
at the top, headers only. One day (and it might be months or it might
be decades from now - that's up to you), when you have mastered C to
the point where high quality C code flows from your brain into your
source without hesitation or flaw, you may find yourself in a
situation a little like that of Peter Seebach, and you may find
yourself thinking "perhaps it's time to break that rule; let's see if
I can't think of an alternative first, though". And, if there is no
sensible alternative, it may then be time to go ahead and break that
rule.
The code in question violated at least a dozen of the hard and fast
rules of programming.
It's a library which is inserted in front of the standard system C library
(using "LD_PRELOAD" for those of you using systems where that makes sense)
which intercepts and modifies essentially all filesystem operations, using
network connections to a server to modify their behavior. The ultimate effect
is that a program running under it *thinks* it has root privileges, and can
do things like changing file permissions, and see the results it expects,
without actually having root privileges. (People familiar with the Debian
build system might think this sounds like fakeroot; it's similar in purpose
but quite different in implementation.)
So I have a couple dozen functions which happen to have the exact same
prototypes *and names* as standard library functions. (You see the madness
already, yes?) And they have preambles and epilogues which are needed
to perform Dire Magics. For instance, handling the fact that open(2) has
two incompatible prototypes; I handle this, very poorly, by relying on
the knowledge that *in fact* you can treat it as a "..." on the systems
we care about.
And then I have things like:
static int
wrap_open(const char *path, int flags, ...) {
int rc = -1;
mode_t mode;
va_list ap;
va_start(ap, flags);
mode = va_arg(ap, mode_t);
va_end(ap);
#include "guts/open.c"
return rc;
}
And the code generates a file looking like:
/*
* int
* wrap_open(const char *path, int flags, ...mode_t mode) {
* int rc = -1;
*/
/* return rc;
* }
*/
Once this is generated, I write the internals for a function with the
given prototype (note the "..." as a warning that there was magic
involved).
Now, the thing is, the code AROUND guts/open.c is subject to change
without notice. If I did some kind of clever thing to rewrite the
source file and try to preserve my code, it would almost certainly
fail eventually, creating nightmarish problems. Worse, there are
real live sound technical reasons to make sure that every one of
these functions is actually in a single file all together, but I don't
want the source for them all in one file necessarily -- in particular,
depending on the system you're building for, some of these functions
might not exist in the machine-generated file. (Also, having them
all in one file, and a few other things in that file, I can declare
a whole mess of things static - very important, because I don't want
to pollute the namespace of programs using my code except on purpose.)
Solution: Use #include of source files which contain only the lines
of code which would go inside a function body, and which are put in
between other lumps of code which are also in that function body, but
which aren't in a user-edited file.
Is this, in general, good style? Not in the least. Is it an effective
way to keep the substantive implementation in clearly-defined source files,
while preserving the Dire Magic of the all-in-one-file.
For another example, look at the SQLite build, where the primary form
of the build (and the only one that is "supported" in most contexts)
is that they have a script carefully read through all their source
and assemble a Giant File Of Source, because with gcc, having all the
source in a single file improves performance somewhere around 10% on
common targets, and reduces the size of the resulting library noticably.
In short: It ain't portable, and it ain't pretty, but there are reasons
to do it for production systems in some cases.
-s