I'm wondering if presented example is safe(no UB) in C:
typedef struct {
uint32_t a;
} parent_t;
typedef struct {
parent_t parent;
uint32_t b;
} child_t;
typedef struct {
uint32_t x;
} unrelated_t;
void test_function(parent_t* pParent) {
((child_t*)pParent)->b = 5U; // downcast is valid only if relation chain is valid
}
int main()
{
child_t child;
unrelated_t ub;
test_function((parent_t*)&child); // valid upcast?
test_function((parent_t*)&ub); // probably UB?
return 0;
}
Due to an explicit cast there is no warranty and good typechecking but as long as proper parameters are passed this should work right?
CodePudding user response:
The rule for casting is https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7 :
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.
Also in case of structures https://port70.net/~nsz/c/c11/n1570.html#6.7.2.1p15 :
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.
And then accessing https://port70.net/~nsz/c/c11/n1570.html#6.5p7 :
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- a character type.
as long as proper parameters are passed this should work right?
There is a vast ocean between "should work" and "is guaranteed to work". The goal is to write code that is guaranteed to work as intended – it has no undefined behavior, because there is no guarantee what happens when it's undefined.
test_function((parent_t*)&child); // valid upcast?
Yes, it's defined behavior. &child
points to &child.parent
, so the resulting pointer has to be properly aligned to parent_t
.
Then (child_t*)pParent
is also valid, because it points to a child_t
object.
Then accessing it ((child_t*)pParent)->
is also valid, because there is a child_t
object accessed with the same type child_t
as it is, so it's a compatible type for sure.
test_function((parent_t*)&ub); // probably UB?
Most probably, casting a pointer from unrelated_t*
to parent_t*
is completely fine – most probably alingof(unrelated_t)
is equal to alingof(parent_t)
, because there are only uint32_t
inside. But, it depends. It may be not valid, when the resulting pointer of (parent_t*)&ub
is not properly aligned to parent_t
.
After that, a similar story with (child_t*)pParent
with the same pointer value inside test_function
. Note that child_t
may have stricter alignment requirements from parent_t
, so while the previous cast may be valid, this one may be not, in the same fashion – depends on alignment requirements of types involved.
Then after that, accessing the value with ((child_t*)pParent)->
is undefined behavior. Accessing a pointer to unrelated_t
object via child_t *
handle is undefined behavior. child_t
is not a compatible type with unrelated_t
.