C is a language designed to reflect the nature of your hardware to some extent for performance reasons. It's also statically-compiled in most cases and that requires compiling and linking the entirety of your codebase in advance. So it doesn't necessarily check every possible human error in advance. Good ones might issue a warning here, but none will generate an error.
At runtime, what your code is doing is invoking undefined behavior. And as others have mentioned in the comments, undefined behavior does not mean a crash. In fact, sometimes a crash is the most desirable behavior that could happen in such cases, since the debugger can immediately give you information about what went wrong. In less ideal scenarios, the program might seem to function fine except once in a while, when a full moon is out, that the behavior changes erratically. It can vary from compiler to compiler, from build setting to build setting, from platform to platform, so you really want to try hard to avoid them when possible.
In practice, segfaults tend to occur less frequently when you're trying to access a pointee for read access than write access, since an operating system's rules for reading from certain sections of memory are usually looser than writing to it. Write access to even another program's memory is usually protected, while read access might only lead to a segfault if the address itself is out of a valid physical range.