Suppose I have some structures defined like:
struct foo { int a; };
struct bar { struct foo r; int b; };
struct baz { struct bar z; int c; };
Does the C standard guarantee that the following code is strictly conforming?
struct baz x;
struct foo *p = (void *)&x;
assert(p == &x.z.r);
The motivation for this construct is to provide a consistent programming idiom for casting to a pointer type that is known to be compatible.
Now, this is what C says about how structures and its initial members are convertible:
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.
C.11 §6.7.2.1¶15
This is what it says about void
pointer conversions:
A pointer to
void
may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer tovoid
and back again; the result shall compare equal to the original pointer.
C.11 §6.3.2.3¶1
And this is what it says about converting between object pointer types:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.
68) In general, the concept ‘‘correctly aligned’’ is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.
C.11 §6.3.2.3¶7
My understanding from the above is that converting an object pointer to an object pointer of a different type via a void *
conversion is perfectly fine. But, I got a comment that suggests otherwise.
Any pointer to an object, optionally type-qualified, can be converted to void* , keeping the same const or volatile qualifications.
A void pointer is a pointer that can point to any type of object, but does not know what type of object it points to. A void pointer must be explicitly cast into another type of pointer to perform indirection. A null pointer is a pointer that does not point to an address. A void pointer can be a null pointer.
Why do we use a void pointer in C programs? We use the void pointers to overcome the issue of assigning separate values to different data types in a program. The pointer to void can be used in generic functions in C because it is capable of pointing to any data type.
Since void is an incomplete type, it is not an object type. Therefore it is not a valid operand to an addition operation. Therefore you cannot perform pointer arithmetic on a void pointer.
Your example is strictly conforming.
The 2nd sentence from §6.7.2.1 ¶15 (A pointer to a structure object, suitably converted, points to its initial member ... and vice versa.) guarantees the following equalities :
(sruct bar *) &x == &(x.z)
(struct foo *) &(x.z) == &(x.z.r)
As you are at the beginning of the struct
, no padding can occur, and my understanding of the standard is that the address of a struct
and of its first element are the same.
So struct foo *p = (void *) &x;
is correct as would be struct foo *p = (struct foo *) &x;
In that particular case, the alignment is guaranteed to be correct per §6.7.2.1 ¶15. And it is always allowed to pass via a void *
, but it is not necessary, because §6.3.2.3 ¶7 allows the conversion between pointers to different objects, provided there is no alignment problem
And it should be noted that §6.2.3.2 ¶7 also says : 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 that means that all those pointers point in fact to the lowest addressed byte of x.r.z.a
. So you could also pass via pointers to char
because we also have :
(char *) &x == (char *) &(x.z) == (char *) &(x.z.r) == (char *) &(x.z.r.a)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With