Hallvard B Furuseth wrote On 05/30/07 14:55,:
Quote:
Eric Sosman writes:
>
Quote:
>>Dave Vandervies wrote On 05/29/07 13:33,:
>>
Quote:
>>>(2b) Is there a better way to do this?
>>
> One possibility would be to use a `void*' as the
>>"externally visible" pointer to the internal struct, and
>>to convert it to a `struct overlapped_read_data*' or to a
>>`struct overlapped_write_data*' internally. Completely
>>gives up on type safety, but does little or no violence to
>>your framework as it stands.
>
Or point to a union which contains both structs. Wastes
memory for the smallest struct though.
It also exposes the details of the no-longer-opaque
structs, making it difficult to change those details at
a later date. (The O.P. did not say in so many words that
opacity was a goal, so I may be reading more than is there.)
Quote:
Quote:
> Another approach might be to rearrange things a bit.
>>Instead of having the overlapped_cookie struct point at
>>different varieties of overlapped_data structures, make
>>the overlapped_cookie be the first element of both flavors
>>of overlapped_data:
>>(...)
>>Thanks to 6.7.2.1p13 it is permissible to convert a struct
>>pointer to a pointer to its first element and vice versa,
>>so an internal function can convert a cookie pointer to
>>a pointer to the struct that contains it.
>
Alternatively, declare both structs in a header file and
make them members of a union in that header. Then thanks
to C99 6.5.2.3p5 it's OK to use the common initial members
of either of the struct types - as long as the union is
in scope (even if you do not use it).
Again, opacity is lost. Also, I don't think this quite
obeys the letter of the law: 6.5.2.3p5 specifically talks
about accessing struct elements inside union objects, not
about free-standing structs of the same types.
I once fixed a bug that had to do with exactly this
distinction. As in 6.5.2.3, we had a bunch of structs with
a common initial subsequence, and a union containing all of
them. Vastly simplified and violently paraphrased:
struct One {
int type; /* always equal to 1 */
int payload;
};
struct Two {
int type; /* always equal to 2 */
double data;
};
union All {
struct One eins;
struct Two zwei;
};
The bug arose when a pointer to a free-standing struct One
instance was passed to a function expecting a union pointer:
void printValue(const union All *ptr) {
switch (ptr->eins.type) {
case 1:
printf ("one: %d\n", ptr->eins.payload);
break;
case 2:
printf ("two: %g\n", ptr->zwei.data);
break;
...
}
}
...
struct One instance = { 1, 42 };
printValue ((union All*) &instance);
At first glance this might look all right, but what
it actually produced was the platform's equivalent of a
bus error. The problem was that the alignment requirement
for a struct Two was stricter than that for a struct One,
so the alignment for a union All was also stricter than
for a struct One. The free-standing struct One instance
was properly aligned for a struct One (of course), but
happened not satisfy the stricter union All requirement.
Unfortunately, the compiler "knew" that its argument would
point to strictly-aligned memory, and generated instructions
that faulted when the actual argument turned out to be too
leniently aligned.
To put it another way: every valid union All pointer
was a valid struct One pointer, but the converse didn't
hold.
Now, this might not be an issue in the O.P.'s case,
because he's using malloc() to obtain memory for instances
of his "variant" structs and malloc()'s alignment suffices
for anything at all. But that doesn't strike me as a safe
practice for the long haul: Someday, somebody may embed a
struct One in a struct BigBox and not put it at the very
start, or somebody may mallocate an array of struct One
instances -- the [0] element will be splendidly aligned,
but what about the [1] element?
From 6.5.2.3p5 and from other parts of the Standard,
we can deduce that the common initial subsequences of
the various structs that appear together in unions must
be arranged identically: You could walk through them with
offsetof() and get the same answers from all variants.
But identical layout isn't in itself enough to avoid
undefined behavior in type punning. And when there's a
perfectly clean (and opacity-preserving) method available,
there seems little incentive to juggle hand grenades.
--
Eric.Sosman@sun.com