char c = -1;
printf("%02X\n", c);
This has undefined behavior.
The %X format requires an argument of type unsigned int. You're passing an argument of type char, which is (almost certainly) promoted to int.
So you're printing a value of type int with a value outside the range of the expected type, unsigned int. This is where you get undefined behavior.
(There's no portable way to print a signed integer type in hexadecimal, unless you write your own code to do it. %x and %X, which print hexadecimal, require an unsigned argument. The decimal formats are %d for signed and %u for unsigned.)
Note that type char itself may be unsigned in some implementations so your initialization
char c = -1;
doesn't have undefined behavior, but it can store an implementation-defined value (probably 255) in c.
If you want to print a byte value in hexadecimal, you probably want to use unsigned char rather than plain char. And you'll want to convert it to the expected type when passing it to printf, because unsigned char is also (almost certainly) promoted to (signed) int).
unsigned char c = 255;
printf("c = %02X\n", (unsigned int)c);
If you want to print a char value in hexadecimal, you can use this slightly convoluted code:
    char c = -1;
    printf("c = %02X\n", (unsigned char)c);
The cast to unsigned char ensures that the value is is in the range 0..255; ensures that the value is not promoted from char to int. The unsigned char value is promoted to int, which is then treated as an unsigned int value by the %02X format. (int and unsigned int arguments are interchangeable, but only for values in the range of both types.)