[comp.lang.c] On Portability

kenny@m.cs.uiuc.edu (10/21/88)

Geez, I wrote that it is wise to avoid int[pointer] and int[array] in
programs which are intended to be portable, and opened a whole can of
worms.  I could almost believe that there's nobody on this group that
ever actually tries to produce portable code!

Someone posted that we're dealing with two different definitions of
portability here.  Yes, one could, in theory, expect that all
compilers will comply with some standard, either the _de facto_ one
presented in K&R, or the dpANS.  The fact remains that some do *not*,
and there are a few easily identified areas where a *number* of
compilers have trouble.

Yes, those compilers are broken.  Consider, though, the point of view
of the person providing software.  Should the provider say, `Sorry, my
programs won't work on your machine because your compiler is broken?'
Perhaps, but that's a lost sale; one small bit of fame and fortune
that that software developer will not receive (GNU people, please
ignore the fortune and consider only the fame 8-) ).  You just don't
go around saying things like that to your customers.

If a *lot* of compilers do something wrong, or if the spec is
particularly unclear on a point, it's wise, if you want to avoid
trouble, to stay away from using that construct.  Some of these are
*perfectly* well defined by one spec or another: it's simply a matter
of experience that they just plain don't work on a lot of compilers.
Frankly, I can do without the hassle of trying to port code that
depends on these things, so I avoid constructs that I know are
problems.

I'm not condoning the compiler writers for producing broken compilers.
But I know that I'll have to deal with the broken compilers, so I
program defensively with respect to the common bugs that I know about.
This isn't saying, `such-and-such might not work, so don't use it,'
(like the example of the *for* loop); it's saying `such-and-such is
known to fail on compilers ABC, DEF, and XYZ; don't use it in code
that you expect to be portable to those environments.'  And the same
bugs keep showing up again and again -- enough so that I can identify
certain areas where the ice is thin.

Some of the things that one or another spec is clear about that have
troubles in real life include:

	- It is unwise for portable programs to depend on the
behaviour of bit fields.  The precise alignment and padding of the
fields are extremely implementation dependent.  The maximum length of
a field also varies from implementation to implementation, and a
declaration like
	struct S {
		unsigned field : 17;
	};
is asking for trouble.

	- It is unwise to depend on the behaviour of ~, &, ^, and |
when applied to signed integers, particularly if they may really be
negative.  There are several `right' ways to do this; it isn't always
clear what the choice should be on a machine with other than a
twos-complement notation.  (I have dealt with one compiler, that shall
remain nameless, on which ~0 == 0 [~(unsigned) 0 is correct, in case
you were wondering].)

	- It is unwise to use &&, ||, !, or casts in constant
expressions.  K&R has a typo in it (in section 15 of the reference
manual).  All the compilers I know about do it right, but someone has
probably implemented it by the book.  This means that some compiler,
somewhere, probably forbids
	#if a == 0 && b == 0
because the book says to.

	- It is unwise to depend on integer+pointer and its friends
working correctly.  K&R is clear on this, if you read carefully;
several compiler writers have not been so careful.

	- It is unwise to use the comma operator to join two
expressions of unlike type.  Beats me where the compiler writers got
the idea that's forbidden, but there are several compilers that forbid
it.

Note that there are easy work-arounds to all of these problems.  It's
not painful to avoid them.  Each of these problems (with the exception
of ! in constant expressions) is present in at least two compilers
that I know about, running on at least two different machines (and no,
I'm not dealing with a sample of two braindead compilers).  Several of
these compilers are quite popular, and there's a significant
probability that someone will be porting my code to these sometime.

davidsen@steinmetz.ge.com (William E. Davidsen Jr) (10/27/88)

  Another thing to avoid is use of the triadic operator to return
expressions of differing types for true and false. Some compilers won't,
and some silently do it wrong. I've never seen one that didn't work as
expected when a cast was used.

ie. m += (a < b ? 0x221 : 14.2);
-- 
	bill davidsen		(wedu@ge-crd.arpa)
  {uunet | philabs}!steinmetz!crdos1!davidsen
"Stupidity, like virtue, is its own reward" -me

guy@auspex.UUCP (Guy Harris) (10/27/88)

>Someone posted that we're dealing with two different definitions of
>portability here.  Yes, one could, in theory, expect that all
>compilers will comply with some standard, either the _de facto_ one
>presented in K&R, or the dpANS.  The fact remains that some do *not*,
>and there are a few easily identified areas where a *number* of
>compilers have trouble.

Let's hope here will be ANSI C test suites when the standard comes out,
so that in the future compilers that have trouble on these things will
be caught and, hopefully, fixed....

>	- It is unwise to depend on integer+pointer and its friends
>working correctly.  K&R is clear on this, if you read carefully;
>several compiler writers have not been so careful.

Do you mean "interger+pointer" as opposed to "pointer+integer"? 
Addition is generally commutative, and if a compiler can't handle
"pointer+integer", it is unwise to purchase that compiler;
"pointer+integer" is a quite widespread construct in C code.