Undefined behavior means that it is not defined by the C specification. It can be very well defined (or partially defined) for a particular compiler.
Most compilers define unsigned shift behavior.
Most compilers determine if arrays of zero length are allowed.
Sometimes you can change bahaviour using compiler flags, for example --pedantic or flags that handle all warnings as errors.
So the answer to your question is:
It depends on the compiler. You need to check the documentation for your specific compiler.
Can you rely on a specific result when you use something that is UB according to the C standard?
It depends on what you code. If this is the code for a specific embedded system, where the likelihood that something will ever be transferred to another place is low, then, in any case, rely on UB if it gives a large income. But it is best to avoid UB whenever possible.
Edit:
is this permissible in any case, since only the result is undefined, or is it "bad", nevertheless?
Yes (only the result of undefined is true in practice, but theoretically, the compiler manufacturer can terminate the program without violating the C specification), and yes, this is bad, nevertheless (since this requires additional tests to ensure that the behavior remains unchanged after changes are made )
If the behavior is unspecified, you can observe what behavior you get. Best if you checked the generated assembly code.
You need to know that behavior can change if you change something. Changes that may change behavior include, but are not limited to, changes to the optimization level and the application of updates or patches to the compiler.
The people who write compilers are usually rational people, which means that in most cases the program will behave as it was easier for the compiler developer.
The best practice is still to avoid UB whenever possible.