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.