Predefined macro namespace

J

J.G.Harston

I've been trying to track down explicit information about this for some time.

I know that the ANSI C standard specifies the __* and _[A-Z]* macro
namespace as reserved for the compiler implementation, and playing with
a handful of compilers I can find what those specific compilers predefine,
but is there any information out there on how that reserved namespace
should be used?

If I am writing some portable code what predefined macros should I expect
to tell me what the compiler is targetting? I've seen almost random
collections of things like __win__ __WIN32__ __WIN __DOS__ __MSDOS__ __unix
__HPIX__ _M68000_ __M68000__ _M68K_ etc.

If I write a fancy new implementation to target a Bambleweeny 57 Sub-Meson
Brain what macros should I make my compiler predefine? Should I predefine
__TEA__, __tea__ or __hottea__ for the targetted i/o system and should the
datastore be __BROWN__ or __brown__ etc????

Thanks
 
K

Kevin Goodsell

J.G.Harston said:
I've been trying to track down explicit information about this for some time.

I know that the ANSI C standard specifies the __* and _[A-Z]* macro
namespace as reserved for the compiler implementation, and playing with
a handful of compilers I can find what those specific compilers predefine,
but is there any information out there on how that reserved namespace
should be used?

If I am writing some portable code what predefined macros should I expect
to tell me what the compiler is targetting? I've seen almost random
collections of things like __win__ __WIN32__ __WIN __DOS__ __MSDOS__ __unix
__HPIX__ _M68000_ __M68000__ _M68K_ etc.

I suppose there's more than one possible meaning for "portable", but I
wouldn't consider code that relies on compiler-specific predefined
macros to be portable. "Portable", at least by my definition, means that
the code will work on any implementation, so there would be no reason to
hide parts of it using preprocessor conditionals.
If I write a fancy new implementation to target a Bambleweeny 57 Sub-Meson
Brain what macros should I make my compiler predefine? Should I predefine
__TEA__, __tea__ or __hottea__ for the targetted i/o system and should the
datastore be __BROWN__ or __brown__ etc????

There is no good, topical answer to your question. These things are
inherently implementation-defined. To the best of my knowledge, the
standard makes no recommendations or requirements aside from what you've
already mentioned.

Personally, I despise #ifdef-riddled code and avoid it at all costs. I'd
recommend using alternative source files for different targets instead.
For example, suppose I have a module called coolstuff.c. Maybe this is a
generic, purely ISO C implementation, but not very efficient. I can make
a more efficient implementation for a particular platform by using
system-specific libraries. So I might create coolstuff_sparc.c,
coolstuff_x86.c, coolstuff_mips.c, etc. Then I compile and link against
whichever is most appropriate for the platform I'm compiling for.

-Kevin
 
R

Randy Howard

I suppose there's more than one possible meaning for "portable", but I
wouldn't consider code that relies on compiler-specific predefined
macros to be portable.

How about "ported"? I.e., in order to get certain types of programs
working on more than one platform, you MUST do platform-dependant things,
then add modules to support each platform that differs from one previously
implemented. That does not mean that 99% of the source can't be
completely free of such, and the remaining 1% requires attention when
adding a new platform not previously tested. I'd suggest that the above
scenario is far better than what is commonly encountered when attempting
to move source to a new platform.
"Portable", at least by my definition, means that
the code will work on any implementation, so there would be no reason to
hide parts of it using preprocessor conditionals.

Such code is very limited by the confines of the standard C "sandbox".
Personally, I despise #ifdef-riddled code and avoid it at all costs. I'd
recommend using alternative source files for different targets instead.
For example, suppose I have a module called coolstuff.c. Maybe this is a
generic, purely ISO C implementation, but not very efficient. I can make
a more efficient implementation for a particular platform by using
system-specific libraries. So I might create coolstuff_sparc.c,
coolstuff_x86.c, coolstuff_mips.c, etc. Then I compile and link against
whichever is most appropriate for the platform I'm compiling for.

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.

IOW, there is no one-size-fits-all answer and blanket statements about
the "one true way" are typically incorrect under examination.
 
K

Kevin Goodsell

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
 
E

Eric Sosman

Kevin said:
I suppose there's more than one possible meaning for "portable", but I
wouldn't consider code that relies on compiler-specific predefined
macros to be portable. "Portable", at least by my definition, means that
the code will work on any implementation, so there would be no reason to
hide parts of it using preprocessor conditionals.

#include <stdio.h>
#include <limits.h>
#if INT_MAX >= 1000000
#define MILLION_FMT "d"
#else
#define MILLION_FMT "ld"
#endif

int main(void) {
printf ("For the %" MILLION_FMT "th time, Hello world!\n",
1000000);
return 0;
}

Any portability problems?
 
K

Kevin Goodsell

Eric said:
#include <stdio.h>
#include <limits.h>
#if INT_MAX >= 1000000
#define MILLION_FMT "d"
#else
#define MILLION_FMT "ld"
#endif

int main(void) {
printf ("For the %" MILLION_FMT "th time, Hello world!\n",
1000000);
return 0;
}

Any portability problems?

Not that I can see, but it also doesn't use the type of macros I was
talking about. My last sentence above isn't as clear as it should have
been, but the first sentence mentions "compiler-specific predefined
macros", by which I meant things like __GNU_C__ and __MSVC_VER (or
however they are actually spelled).

-Kevin
 
D

Dan Pop

In said:
#include <stdio.h>
#include <limits.h>
#if INT_MAX >= 1000000
#define MILLION_FMT "d"
#else
#define MILLION_FMT "ld"
#endif

int main(void) {
printf ("For the %" MILLION_FMT "th time, Hello world!\n",
1000000);
return 0;
}

Any portability problems?

No, but you're actually making his point. The code can be rewritten in
a much more readable fashion with no loss of portability:

#include <stdio.h>

int main(void) {
printf("For the %ldth time, Hello world!\n", 1000000L);
return 0;
}

Your example also shows that not being able to rely on a given type for
a given purpose destroys the code readability. If hardcoded long is
not an option for your example, it may be worth considering a trade
off between portability to obscure platforms and readable code:

#include <stdio.h>
#include <limits.h>
#if INT_MAX < 1000000
#error "This program requires a wider int"
#endif

int main(void) {
printf("For the %dth time, Hello world!\n", 1000000);
return 0;
}

Such a trade off was probably out of question 15 years ago, but times have
changed...

Programming is more a matter of making the right choices than of following
the "right" religion.

Dan
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,533
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top