[comp.lang.c] A similar lint question

dlc@dlc.fac.cs.cmu.edu (Daryl Clevenger) (12/01/88)

I am by no means a novice C user and have the pleasure of knowing some
very competent programs here at work.  Much of my knowledge of what is
portable and correct C came from this news group and its existence I
consider invaluable. There are still a few areas where I need clarification.
I will try to phrase the questions as clearly as possible, but I do not
think of them as naive.  I am trying to consider them from "basic principles",
if such a notion can be applied to C, similar to examining something in
physics from "basic" or "axiomatic" natural laws.

First, a lint question along the lines of the current discussion.
Whenever I write code from scratch, I use lint before I ever
try to compile it and fix as many of the warnings/errors as I
can (modulo those things that lint will not shut up about).  As a
result, I give practically every paranoia flag to lint as possible,
namely '-hca' (no -p, it seems to cause more problems than it
is worth and I really don't care about GCOS implementations :-)).

The question arises concerning casts and really leads to some generic
questions about them.  Let's say you have the following code fragment
(assume all the correct include files and correct assignments/code in
the ellipses):

    /* Exhibit 1 */
	{
	    .
	    .
	    .
	    struct sockaddr_in saddr;
	    int s, len;
	    .
	    .
	    .
	    len = sizeof(struct sockaddr_in);
	    if (bind(s, (struct sockaddr *)&saddr, len) < 0) {
		perror("bind");
		exit(1);
	    }
	    .
	    .
	    .
	}

Every time I lint this, I get "warning: illegal structure pointer combination"
for the bind() call.  Here is the lint prototype from llib-lc:

    int bind(s, n, l) struct sockaddr *n; { return 0; }

The questions are:

    1) Given 2 structure pointers, is it portable to cast from one to
       the other?  I remember a discussion a while back and I believe
       that the consensus was that "struct <name> *" pointers must
       "smell the same" or problems may result.  My hunch guess is
       that technically structure pointer casts are not *DEFINED* to be
       always portable, but in practice, they are (see further
       discussion below).

    2) In general, if a pointer cast meets the following constraints
       from K&R 1st Ed., page 210, sec 14.4 (We love quoting this don't
       we :-))

	    "A pointer to one type may be converted to a pointer to
	another type.  The resulting pointer may cause addressing
	exceptions upon use if the subject pointer does not refer
	to an object suitably aligned in storage.  It is guaranteed
	that a pointer to an object of a given size may be converted
	to a pointer to an object of a smaller size and back again
	without change."

       (Note: from the above quoted paragraph, I assume that "smaller size"
	      really means "less than or equal size."  If this is not
	      the case then replace the following occurences of "<="
	      with "<" and accordingly warp you brain to follow the
	      questions.)

    From this I conclude:

       For non-{floating,pointer} scalar types, if we have type1 *t1,
       type2 *t2, type1 *tmp and
       sizeof(type2) <= sizeof(type1), then the following is guaranteed

       /* Exhibit 2 */
	    tmp = t1;		  /* tmp <==> t1 */
	    t2 = (type2 *)t1;	  /* No usage to cause addressing exceptions;
				     assignment only for effect */
	    t1 = (type1 *)t2;
	    if (tmp == t1)	  /* This is guaranteed from last statement
	        printf("True\n");    in quoted passage. */

       This is the easy case, since a partial ordering is defined on
       the set of scalar types such that, (let s <- sizeof operator)
       
       /* s(void) <= */ s(char) <= s(short) <= s(int) <= s(long)
       
       (NB: I did not include floating point types because I feel that
	    there is no guarantee what the sizeof(float) is, except
	    /* s(void) <= */ s(char) <= s(float).  Pointer types
	    are not included since the rules governing them can be
	    easily deduced once this mess is made clear :-))

       The problem I see (and my own answer to (1), but I would like
       confirmation/denial) is with aggregate types (using structures
       as an instance).
       
       Given struct type1 *t1, struct type2 *t2 and struct type1 *tmp,

	    if (sizeof(struct type2) <= sizeof(struct type1) then
	        the code fragment under /* Exhibit 2 */ still is
		guaranteed to work;
	    otherwise,
		all bets are off.

	    (I am assuming the same is true for other aggregate types.
	     Lint still "fails" to get this right if it is indeed true.
	     When types differ, lint complains about all struct pointer
	     casts, regardless of the sizes of the structure objects.)

In none of the above discussion am I considering the act of *derefencing*
any of these pointers; that is an entirely different question whose
answer I understand :-).  I am only talking about explicit coercions
between pointer types.  My gut feeling is that struct pointer casts must
obey the above object size rules to portably work, but in practice all
struct pointers "smell the same."


    3) Given pointer, p, obtained via legal coercion, is it safe, i.e.
       no exception will be raised, to deference p if p is suitably aligned?
       The results of derefencing such a pointer, p, may not be meaningful
       in any portable way, depending on the type of (*p) and the type from
       which p was obtained.  Namely, does there exist 2 data types, T1
       and T2, such that
       
           T1 *s, T2 *p;
	   .
	   .
	   .
	   p = (T2 *)s;  /* Assume s is properly initialized and the result
			    of the cast is a legal in all aspects *pointer*
			    of type T2. */

	   derefence(p), where derefence is a proper operator for type
	   T2 *, will *ALWAYS* have both a consistent and meaningful
	   result i.e. is 100% portable.

I realize that (3) is a rather nebulous question, but I am interested in
"correct" answers i.e. answers as defined in K&R or the ANSI Draft Standard
with "answers according to reality" as parenthetical answers.

    4) Is my interpretation of the quoted paragraph from K&R accurate,
       or am I totally wacked in the head and need to seek professional
       help.

This is all of the questions, and the rest can be ignored and is merely
comments about this group.  Pardon the run on sentences.

The first few years, all I knew about C was what was in K&R 1st Ed. with
contributions from my boss.  The only environment I used was a 4.1BSD
VAX.  Then I began reading this newsgroup, and I was enlightened :-).
The many contributions from Doug Gwyn, Karl Heur, Henry Spencer and
Chris Torek (as well as others that are only omitted because I can
not recall their names) have been invaluable to me.  Many of the "dark
corners" of C were lit for me and I realized that I never *really*
understood the concept of portable C code until I read this group.
My appreciation of the time contributed by the "regulars" in this group
to edify the world can not be overstated.

Finally, while composing this article, I realized that I should have
saved and should save all the answers, especially from the above four
individuals, and use them to "educate" all the people I encounter that
write stupid/bad C code.  In fact, maybe I should go on an apostolic
crusade of the world for the benefit of spreading the truth about
correct and portable C code :-).  Well, maybe not until I am caught
up at work.

--------
Daryl Clevenger
Carnegie-Mellon CS/RI Facilities Staff

	ARPA: dlc@cs.cmu.edu
-- 

gwyn@smoke.BRL.MIL (Doug Gwyn ) (12/02/88)

In article <3737@pt.cs.cmu.edu> dlc@dlc.fac.cs.cmu.edu (Daryl Clevenger) writes:
>	    struct sockaddr_in saddr;
>	    if (bind(s, (struct sockaddr *)&saddr, len) < 0) {
>Every time I lint this, I get "warning: illegal structure pointer combination"
>for the bind() call.  Here is the lint prototype from llib-lc:
>    int bind(s, n, l) struct sockaddr *n; { return 0; }
>    1) Given 2 structure pointers, is it portable to cast from one to
>       the other?  I remember a discussion a while back and I believe
>       that the consensus was that "struct <name> *" pointers must
>       "smell the same" or problems may result.

The conversion is always permitted, but use of the resulting pointer
is valid only if the original pointer was to an object suitably aligned.
Structure pointers all must have the same representation (this can be
deduced from other requirements), but not all structure types need be
aligned the same.  Thus "lint" is right to warn about this.  The ideal
solution here is to use a union of the two structure types, and pass
the address of the appropriate union member to the function.

>    2) ...

It is the object alignments that matter, not their sizes (K&R 1st Ed.
was not sufficiently precise).  In general you cannot deduce alignment
from size or vice versa.

>    3) Given pointer, p, obtained via legal coercion, is it safe, i.e.
>       no exception will be raised, to deference p if p is suitably aligned?

No, for example the size of the supposed object pointed to after the
conversion may exceed the size of the storage actually allocated.

There are portable uses of pointer conversion.  The general rule of
thumb is, if a pointer to a type really does point to an object of
that type, it may safely be dereferenced.  Otherwise it depends on
the specific circumstances.  Simply converting a pointer does not
in itself impose any sanity requirements on the resulting pointer.