The construct you describe of using a pointer to a "base" structure as an alias to several "derived" structures, while often used with things like struct sockaddr
, is not guaranteed to work by the C standard.
While there is some language to suggest is might be supported, particularly 6.7.2.1p15:
Within a structure object, the non-bit-field members and the
units in which bit-fields reside have addresses that increase in
the order in which they are declared. A pointer to a structure
object, suitably converted, points to its initial member (or
if that member is a bit-field, then to the unit in which it
resides), and vice versa. There may be unnamed padding within
a structure object, but not at its beginning.
Other parts suggest it is not, particularly 6.3.2.3 which discusses pointer conversions that are allowed:
1 A pointer to void may be converted to or from a pointer to any object type. A pointer toany object type may be converted to a
pointer to void and back again; the result shall compare equal
to the original pointer.
2 For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type;
the values stored in the original and converted pointers shall compare
equal.
3 An integer constant expression with the value 0, or such an expression cast to type void *, is called anull pointer
constant. If a null pointer constant is converted to a pointer type,
the resulting pointer, called a null pointer, is guaranteed to compare
unequal to a pointer to any object or function.
4 Conversion of a null pointer to another pointer type yields a null pointer of that type.Any two null pointers shall
compare equal.
5 An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined,
might not be correctly aligned, might not point to an entity
of the referenced type, and might be a trap representation.
6 Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the
result cannot be represented in the integer type,the behavior
is undefined. The result need not be in the range of values
of any integer type.
7 A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly
aligned for the referenced type, the behavior is undefined.
Otherwise, when converted back again, the result shall compare
equal to the original pointer. When a pointer to an object is
converted to a pointer to a character type,the result points to the
lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining
bytes of the object.
8 A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare
equal to the original pointer. If a converted pointer is used to call
a function whose type is not compatible with the referenced type,the
behavior is undefined.
From the above is does not state that casting from one struct to another where the type of the first member is the same is allowed.
What is allowed however is making use of a union
to do essentially the same thing. Section 6.5.2.3p6 states:
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common
initial sequence (see below), and if the union object
currently contains one of these structures, it is permitted
to inspect the common initial part of any of them anywhere that a
declaration of the completed type of the union is visible. Two
structures share a common initial sequence if corresponding
members have compatible types (and, for bit-fields, the same widths)
for a sequence of one or more initial members.
So what you can do is define a union that contains all the possible types as well as the base type:
union various {
struct base { int num; } b;
struct one { int num; int a; } s1;
struct two { int num; double b; } s2;
struct three { int num; char *c; } s3;
};
Then you use this union anyplace you need of of the three subtypes, and you can freely inspect the base member to determine the type. For example:
void foo(union various *u)
{
switch (u->b.num) {
case 1:
printf("s1.a=%d\n", u->s1.a);
break;
case 2:
printf("s2.b=%f\n", u->s2.b);
break;
case 1:
printf("s3.c=%s\n", u->s3.c);
break;
}
}
...
union various u;
u.s1.num = 1;
u.s1.a = 4;
foo(&u);
u.s2.num = 2;
u.s2.b = 2.5;
foo(&u);
u.s3.num = 3;
u.s3.c = "hello";
foo(&u);