I don't get any of the present answers. The C standard clearly says that right-shifting a negative number is implementation-defined behavior. It is not unspecified behavior, which means something else. As you correctly cite (C17 6.5.7 §5):
The result of E1 >> E2 is E1 right-shifted E2 bit positions. /--/
If E1 has a signed type and a negative value, the resulting value is implementation-defined.
This means that the compiler must document how it behaves. Period.
In pratice: the document must tell if the compiler uses arithmetic right shift or logical right shift.
This is as opposed to unspecified behavior, which is implementation-specific behavior that does not need to be documented. Unspecified behavior is used in two cases:
- When the compiler behavior might be an implementation secret that the compiler vendor should not be forced to reveal to their competitors.
- When the compiler can't be bothered to document how the underlying details such as OS and RAM memory cells work.
For example, a compiler does not need to document the order of evaluation in code like this:
a = f1() + f2();
a += f1() + f2();
Documenting the order in which the sub-expressions are evaluated would reveal details about how the compiler's internal expression tree and optimizer work, which in turn would reveal why a compiler produces better code or compiles faster than the competition. This was a big thing when the C standard was originally written. Less so nowadays when there's some great open-source compilers, so it is no longer a secret.
Similarly, a compiler does not need to document what this code prints:
int a;
int ptr = &a;
printf("%d", *ptr);
a
is an indeterminate value and the output is unspecified - in practice the output depends on what was stored in that particular RAM cell before. What we would call a "garbage value". (Before yelling "UB", see (Why) is using an uninitialized variable undefined behavior?).