malloc just allocates empty space (with no assumption about the object that will be put there)
Correct. Dynamically allocated memory specifically, has no type until the point where you write something to that area. Formally the C language calls this the effective type. The formal definition is found in C17 6.5/7:
The effective type of an object for an access to its stored value is the declared type of the object, if any. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.
What's returned from malloc is just a raw chunk of memory, with no special attributes, until the point where you write to that area. After which the compiler has to put a "type label" on it internally. As soon as you access it by using [], the compiler will have to assume that the data allocated has to be treated as an array, to keep the type system consistent between statically allocated and dynamically allocated objects.
Similarly, the memory area becomes a struct at the point when you access the memory, as it will have padding etc and dictate the memory offset of each member. So if given a struct with opposite order of your example, like this:
struct test {
char a;
int b;
};
Then it is implementation-defined if x->b
will result in access to byte 1, byte 4 or something else, since the compiler is free to add padding between the members.
But as soon as you access x->something
, the compiler will have to start regarding whatever x
points at as effective type struct test
, or the type system wouldn't behave consistently.