Is it allowed to use a pointer to some type as a pointer to a different type if they have otherwise-identical pointer members that differ only in the constness of the pointed-to values?
Concretely, given the following two structures:
struct type {
char *a;
int *b;
};
struct const_type {
const char *a;
const int *b;
};
Is it allowed to treat a pointer to type as a pointer to const_type and vice-versa1, for example passing a type pointer to a function expecting a const_type pointer as shown:
int add(const const_type* t2) {
return *t2->a + *t2->b;
}
int is_it_legal() {
int some_int = 42;
char blah[] = "six times and counting...";
type t1 = {blah, &some_int};
return add((const_type*)&t1);
}
My overall motivation here is to have a foo and const_foo struct which differ only in the constness of the objects pointed to by their contained embedded pointers. For any function that doesn't modify the pointed-to data, I would like to write a single function (that takes the const_foo variant) and have it work for both types of objects.
1Evidently in the latter case (using a const_type as a type it is not be safe if it results in an attempt to modify a pointed to value in case they were defined const, but you may assume this doesn't occur).
No, the behavior is not defined by the C standard. (Although it is “allowed” in various senses, including that a C implementation may support this as an extension.)
First, C 2011 6.7.3 10 says “For two qualified types to be compatible, both shall have the identically qualified version of a compatible type…” So const char and char are not compatible.
Then 6.7.6.1 says “For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.” So const char * and char * are not compatible.
Next, 6.2.7 1 says “Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: … If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types;…” Also, it requires “If one is declared with a tag, the other shall be declared with the same tag.” So the structures are not compatible.
Finally, 6.5 7 tells us “An object shall have its stored value accessed only by an lvalue expression that has one of the following types: a type compatible with the effective type of the object, a qualified version of a type compatible with the effective type of the object,…” The first of these does not apply since the structures are not compatible. The second says you can refer to a structure using a const version, which unfortunately does not apply since the const here qualifies the members, not the structure.
It's not even guaranteed to work if the two structs have exactly the same member list. The code is a strict aliasing violation, accessing type1 via an lvalue of type type2.
As always for strict aliasing discussions: the text of the rule is underspecificed, however it's widely accepted that x->y, which is defined as (*x).y, counts as an access of *x for the purposes of strict aliasing. The rationale for this can be seen in this code:
void f(struct A *a, struct B *b)
{
a->x = 5;
b->y = 6;
}
The compiler should be free to assume that a->x and b->y cannot alias each other , even if they both happen to have type int, and regardless of their offsets within the struct.
However, if you declare a union containing the two struct types, and that declaration is visible at the point of the attempted aliasing, then this bypasses the strict aliasing rule, even if the objects being aliased are not currently in a union!
See this thread for good coverage of the topic, with commentary about compiler compliance.
Back to your question though: since your structs don't even have the same member list, the text from 6.5.2.3/6 about common initial sequences in unions:
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.
does not apply; the two types are just unrelated struct types. (The definition of "compatible type" is in 6.2.7 - changing qualifiers makes the types incompatible). And putting them in a union doesn't gain anything.
Finally though, as discussed on the linked thread; what the standard says and what compilers actually do are not the same thing in this area.
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