The C Standard uses the term "object" to describe at least three different concepts: a sequence of consecutively-addressed bytes which are exclusively allocated for a particular purpose, any combination of address and type which could be used to identify an allocation or portion thereof, regardless of whether it actually is used, or a combination of address and type that have been or will be used within some context to access storage or derive another object [same definition] that has been or will be used in such fashion. The distinction between the these definitions didn't matter in the language described by K&R's book The C Programming Language, but the Standard added rules which make it necessary to distinguish those meanings, without offering the terminology necessary to draw such distinctions.
When N1570 p6.5p7 of the C Standard says that an object may only have its stored value accessed via lvalues of certain types, it's unclear what exactly it means by "object". If it was referring to the second sense of "object", almost any access to anything made with a non-character-type lvalue would invoke UB. It would probably make more sense to use the third sense of "object" when interpreting that text, but such "objects" would have an active lifetime that differs from that of the underlying storage.
In your example, there are three allocations--x
, y
, and z
. If sizeof (int)
is 4 and sizeof z
is 8, then after unsigned char *p = (unsigned char*)&z.b
, there would exist an object of type unsigned char
identified by *p
, but the Standard is unclear whether the pointer conversion would have caused an unsigned char
object to exist where none had previously, or merely merely yielded a pointer to an unsigned char
object that was created when z
was created and will exist as long as z
does.