There are actually 3 categories:
- Implementation Defined: the implementation (compiler, standard library, execution environment) has to document what happens. Code relying on this is not portable.
- Unspecified: the implementation can choose to do what makes sense, and not tell you. Even reverse-engineering and relying on what you found out, is unreliable. The actual address returned by malloc is unspecified; is it aligned? Does it always grow in value if nothing was free-ed? You shouldn't even care about this detail, so the standard leaves it unspecified.
- Undefined Behaviour: you wrote something that doesn't make sense, if you get lucky the compiler/standard library/operating system will react in a sensible way, but the standard says it's not the implementation's fault you get something wrong as a result. Things like reading variables before initializing them.
Diagnosing UB can be too demanding from the implementation, so the standard doesn't even require it. How would you diagnose incorrect usage of realloc? Add run-time checks? Write a special rule in the compiler so it knows about realloc? Extend the language with metadata? What if realloc is hidden behind a user-defined function? At some point you have to stop, otherwise you could even solve the halting problem.