This is called "usual arithmetic conversions" by the standard and applies whenever two different integer types occur as operands of the same operator.
In essence what is does
- if the types have different width (more precisely what the standard
calls conversion rank) then it converts to the wider type
- if both types are of same width, besides really weird architectures,
the unsigned of them wins
Signed to unsigned conversion of the value -1 with whatever type always results in the highest representable value of the unsigned type.
For line (1) the outcome depends on how wide long and int are. If int is narrower than long, all unsigned values fit in long, and so the conversion stops at long for the RHS. The outcome then is "A". If they are of same width, the conversion continues to unsigned long for both sides and the outcome is "B".
For your special case for short there is also a feature called "integer promotions" that promotes all types that are narrower than int to int. In your lines 3 and 4 you'd have that the expressions on the LHS are first converted to int, which keeps the value unchanged, and then for (3) to unsigned int and for (4) to unsigned long.
According to that my platform (linux, gcc) correctly prints "ADFH" with your code.
The result "BDEH" would occur on a platform that first has long and int of the same width and has the range of unsigned covered by the range of int, that is which for unsigned just ignores the sign bit of int. I didn't know that such platforms still exist.
Some years I wrote a blog post on the anatomy of integer types, which should still be valid.