"Since I want to work with the original bit patterns from a file, how can I convert / cast a signed char to an unsigned char so that the bit patterns remain unchanged?"
As explained in the previous answer to your question on the same topic, any small integer type, whether it is signed or unsigned, will be promoted to type int whenever it is used in an expression.
C11 6.3.1.1
"If int can represent all the values of the original type (as limited in width for the bit field), the value is converted to int; otherwise, it is converted to unsigned int. These are called whole promotions.
Also, as explained in the same answer, integer literals are always of type int .
Therefore, your expression will be compressed to the pseudo-code (int) & (int) & (int) . The operations will be performed on three temporary variables int, and the result will be of type int.
Now, if the source data contains bits that can be interpreted as sign bits for a particular signature representation (in practice, this will be two additions in all systems), you will have problems. Since these bits will be preserved when moving through a char subscription to int.
And then the bit-wise and operator executes AND on every single bit, regardless of the contents of its integer operand (C11 6.5.10 / 3), whether it is signed or not. If you have data in the signed bits of the original signed char, it will be lost. Since integer literals (0xC0 or 0x80) will not have bits that correspond to signed bits.
The solution is to prevent the transmission of sign bits into a temporary int. One solution is to cast c [i] into an unsigned char that is fully defined (C11 6.3.1.3). This will tell the compiler that "the entire contents of this variable is an integer, there are no signed bits."
Even better, I’m used to always use unsigned data in all forms of bit manipulation. Purist, 100% safe, the MISRA-C method, able to rewrite your expression, is this:
if ( ((uint8_t)c[i] & 0xc0u) & 0x80u) > 0u)
The suffix u actually forces the expression to an unsigned int, but it is always recommended to apply it to the intended type. He tells the reader the code, "I really know what I'm doing, and I also understand all the weird implicit rules of promotion in C".
And then, if we know our hex, (0xc0 & 0x80) pointless, this is always true. And x & 0xC0 & 0x80 always matches x & 0x80 . Therefore simplify the expression:
if ( ((uint8_t)c[i] & 0x80u) > 0u)
Is there a list of these implementation-specific aspects elsewhere
Yes, standard C conveniently lists them in Appendix J.3. The only implementation aspect that you encounter in this case is the implementation of the integrity of integers. In practice, these are always two additions.
EDIT: The cited text in the question refers to the fact that various bitwise operators will get the results defined by the implementation. This is briefly referred to as an implementation, defined even in the application, without exact references. Actual chapter 6.5 doesn't say much about how and how: etc. The only operators that explicitly state this are <and →, where the left offset of a negative number is even undefined behavior, but the right offset is an implementation.