You're allowed to access any type via a char buffer. But the converse is not true (quoting https://eel.is/c++draft/expr#basic.lval-11):
> An object of dynamic type Tobj is type-accessible through a glvalue of type Tref if Tref is similar ([conv.qual]) to: Tobj, a type that is the signed or unsigned type corresponding to Tobj, or a char, unsigned char, or std :: byte type. If a program attempts to access ([defns.access]) the stored value of an object through a glvalue through which it is not type-accessible, the behavior is undefined.
The dynamic type of a char buffer is, well, a char buffer, and can only be accessed via things that are the same type as a char buffer up to signedness and cv-qualification. The actual strict aliasing rules are not commutative!
Malloc returns a buffer and then you cast it to the type you want. Similarly for all memory allocators.
Punning the same region of char buffer as two different types is a bit different.
Malloc returns memory that is uninitialized and has no type. The effective type of that memory is initialized on first use by C, whereas C++ relies on angelic nondeterminism to magically initialize the type at return type to whatever will work in the future.
You can’t cast the buffer — you’re casting the pointer.
One might argue (I’m not sure whether this is correct) that malloc allocated an array of ints and the language merely has no way to state that directly. Then you write to those ints using a char pointer, and then you access them as ints, but they’ve been ints ever since allocation.
See https://eel.is/c++draft/intro.object#def:object,implicit_cre....
https://eel.is/c++draft/intro.object#15 is an example showing this with malloc; the subsequent paragraph further permits it to work with an unsigned char array.
2. It doesn't initialize the object that is implicitly created, even if the storage has initialized chars.