Randy said:
Such code is very limited by the confines of the standard C "sandbox".
True. There seems to be 3 different categories of things you might want
to do in a C program: 1) Things that can be done well in straight C. 2)
Things that can be done, though somewhat poorly, in straight C. 3)
Things that simply cannot be done without extensions.
My thinking at the moment is that you could isolate those parts that
fall into categories 2 and 3, and provide implementations that work as
well as possible. For 2, this would mean that they use the
unsatisfactory but standard methods. For 3 I'm thinking that you could
abort with a diagnostic ("not implemented yet"), continue with a
diagnostic, or fail to compile, and choose which of these should occur
based on configuration option.
This is a great solution in cases where the implementations are
dramatically different. On some things though, the only difference
between two platforms might be the header to be included, or a three lines
of initialization code followed by the rest of it being identical. I
don't see a point in having two copies of the source file with the only
difference being trivial. That just makes maintenance a nightmare.
I completely agree. That would be a disaster. Maintaining separate
nearly identical sources is a very bad idea. I'm talking somewhat
theoretically here, because I don't have much experience writing
portable code that requires a lot of non-standard extensions. I did use
a technique like this with a great deal of success a while back, while
writing networking layer that was initially intended to run on a
8051-based single-board computer, and on a PC for testing purposes (it's
much easier to test on a PC). I was surprised at how well this worked. I
didn't even bother to compile or test on the 8051 until after it was up
and running on the PC. I expected it to take up to a week to get it
running on the 8051. It took about half a day.
Even so, in hindsight I don't feel that I took exactly the right
approach. Basically I defined a set of "primitive" functions that would
have to be re-implemented for each target. These functions were very
low-level. Now I think that they were *too* low-level. Because of that,
they were quite easy to implement for a new target (because each did a
single, simple task), but they didn't really create an ideal interface.
The main system was uglier because it had to use an interface that was,
for lack of a better word, unnatural. In retrospect, I definitely should
have made the interface a higher priority.
Getting back to the main topic, your point is well taken. But even so, I
would avoid lines of code that look like this:
#if defined(__SYSTEM_A__)
DoInitStep1();
DoInitStep2();
DoInitStep3();
#elif defined(__SYSTEM_B__)
do_B_init();
#endif
/* 500 common lines follow */
Init() may be a candidate for a target-specific function, though it
would be very short in some cases, and it may not fit well in any
existing target-specific source file. Obviously creating a new source
file for every trivial thing that doesn't fit somewhere else could get
ridiculous.
Another option is to define Init conditionally:
#if defined(__SYSTEM_A__)
# define Init() do { DoInitStep1(); DoInitStep2(); \
DoInitStep3(); } while (0)
#elif defined(__SYSTEM_B__)
# define Init() do_B_init()
#endif
The advantage here is that, while it's still ugly, the ugliness is
isolated from the main part of the source, thus leaving the part that
does the real work a bit less cluttered. If Init() is going to be used
in more than one place, then this might prevent a lot of ugliness.
Finally, another alternative might be to factor out the commonality and
have some sources that are target-specific, but shared between some
similar targets.
IOW, there is no one-size-fits-all answer and blanket statements about
the "one true way" are typically incorrect under examination.
Yes, but there are more- and less-preferable methods. Personally I see
avoiding code with a lot of #ifdefs as a good goal, but, as always,
there are many things to consider and it may be that a #ifdef is the
best choice (or the least bad choice).
-Kevin