1

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?

selbie
  • 100,020
  • 15
  • 103
  • 173
akimata
  • 131
  • 1
  • 7
  • 1
    *"...this should work."* What is supposed to work? If you mean is the relative position of `child` and `ub` in memory guaranteed – no. – Weather Vane Dec 28 '21 at 07:23
  • @WeatherVane I think they're asking if `parent` is guaranteed to be at offset 0 whithin `child`, and whether the pointer casts in both directions are valid. – HolyBlackCat Dec 28 '21 at 07:28
  • 1
    @akimata when you say "should work" what is intended to happen? Please show the expected result with an output. What does "inherit, upcasting and downcasting" mean in C? – Weather Vane Dec 28 '21 at 07:30
  • @HolyBlackCat - Exactly what i was asking for, if i can safely cast up and down within a child. Ub is just an example where compiler won't moan but the behavior is pretty sure undefined and i'm not sure if there is a cleaner/safer solution to catch such mistakes. – akimata Dec 28 '21 at 07:34
  • Are you aware of `union`s? – Yunnosch Dec 28 '21 at 07:49
  • Related: [Cast struct pointer to another struct](https://stackoverflow.com/questions/51757117/cast-struct-pointer-to-another-struct) with this [comment](https://stackoverflow.com/questions/51757117/cast-struct-pointer-to-another-struct#comment90478488_51758304). – Weather Vane Dec 28 '21 at 07:52
  • @WeatherVane Not fully, as there is no downcasting mentioned, only upcasting – akimata Dec 28 '21 at 09:18
  • https://stackoverflow.com/a/51762793/9072753 should answer your question. `downcasting mentioned, only upcasting` it does not matter (?) – KamilCuk Dec 28 '21 at 09:21
  • As already mentioned, the answer you guys are proposing mentions upcasting which is fine but i need a confirmation that downcasting (casting from base to derived one). – akimata Dec 28 '21 at 09:29
  • C does not define nor distinguish _upcast_ nor _downcast_. What OP has here, C calls _casting_. – chux - Reinstate Monica Dec 28 '21 at 10:32

1 Answers1

5

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.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Alright, so it is safe to upcast and downcast structure pointers if they're related to each other like parent_t and child_t. – akimata Dec 28 '21 at 09:59
  • 1
    It's really unrelated... I wanted to convey the information: it's safe to do any cast as long as alignment is ok. You can do `(uint16_t*)&ub`. It's safe to _access_ data with compatible handle only. In this case, a pointer to the structure points to it's initial member, so both accesses are fine. – KamilCuk Dec 28 '21 at 10:01
  • @KamilCuk, why is the "alignment is ok" sufficient for a cast to be safe? The safety of `test_function((parent_t*)&child);` follows from your quotations from the standard, but it only mentions alignment in the pointer _conversion_; as for _dereferencing_ a pointer, it requires that the types are compatible, doesn't it? – Kolay.Ne Jul 29 '23 at 13:33
  • `why is the "alignment is ok" sufficient for a cast to be safe?` becuase `it only mentions alignment in the pointer conversion`, so the cast, i.e. pointer conversion, is safe when alignment is ok. I do not understand. `as for dereferencing a pointer...?` yes. – KamilCuk Jul 29 '23 at 21:09