In the code below, why the returned pointer p is allowed to change?
Because the type of p is int *. Since the pointer is not const, it may be modified. If you want p to not be modifiable, then you must make it const: int * const p
If f instead returns "const int *", then the compiler will complain type mis-match error at line "int *p =f()"
Pointer-to-const is not convertible to pointer-to-non-const. Such conversion is not allowed by the language because it would break const correctness.
Why does it do the same for "int * const"?
I guess you mean to ask "Why does the compiler allow assigning int * const to a int * variable?"
Copying a const object to a non-const object does not break const-correctness. When p is non-const, it's OK to modify it and the const temporary returned by f remains unmodified through it's entire (short) lifetime.
Constness of a temporary is rarely meaningful, and so is returning a top-level const value.