I have this code:
unsigned char *command = "0000";
unsigned char foo = (hex_char_to_int(command[0]) << 4) | hex_char_to_int(command[1]);
unsigned char bar = (hex_char_to_int(command[2]) << 4) | hex_char_to_int(command[3]);
printf("foo: %02x, bar: %02x\r\n", foo, bar);
It uses this function:
unsigned char hex_char_to_int(unsigned char ch) {
switch (ch){
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': return 0xA;
case 'B': return 0xB;
case 'C': return 0xC;
case 'D': return 0xD;
case 'E': return 0xE;
case 'F': return 0xF;
case 'a': return 0xA;
case 'b': return 0xB;
case 'c': return 0xC;
case 'd': return 0xD;
case 'e': return 0xE;
case 'f': return 0xF;
default: return 0;
}
}
This is the result:
"JW\xd6\x96'$$LK\x90\xbbar: 3030\r\r\n"
This is on the Keil C51 compiler, on an AT89C55WD, with printf()
going over a serial port.
What is going on?
EDIT
I change the printf line to
printf("%02x%02x\r\n", (unsigned int)foo, (unsigned int)bar);
So it looks like a bug in printf
. Please, programmers, never make a debugging tool that lies. I beg you.
As far as I can tell, that code should work under any conforming C compiler.
I haven't used Keil C51, but I've seen some indications that it doesn't entirely follow the requirements of the C standard, for example in promoting narrow types.
(This answer previously included a number of possible suggestions, most of which didn't pan out. If you're curious, see the edit history.)
Apparently an
unsigned char
argument passed toprintf
is not promoted toint
orunsigned int
, as the c standard requires.To work around this while keeping your code reasonably portable, add casts to explicitly convert the values of
foo
andbar
tounsigned int
:(The
\r
normally wouldn't be necessary, since\n
is automatically converted to the system's line ending sequence for text streams, but perhaps Keil C51 works differently.)Again, it should work either way, but this change might work around a
bugfeature of Keil 51.UPDATE :
I just checked the online documentation for Keil C51. The documentation for printf shows some non-standard features, including
b
andB
to specifychar
types, just asl
specifieslong
types.b
andB
are not necessary in standard C, since it's not possible to pass achar
(orunsigned char
, orsigned char
) argument toprintf
; any such argument will be promoted toint
, or possiblyunsigned int
. I infer from this, and from the error you've run into, that Keil C51 doesn't promote narrow arguments to variadic functions, and in particular that anunsigned char
argument is not promoted either toint
or tounsigned int
.That explains why
didn't work, and why
did.
This compiler targets a small 8-bit microprocessor. It makes sense that you wouldn't want to implicitly widen single-byte arguments. The authors apparently chose performance over conformance -- which is a perfectly valid decision. (It would be nice if the documentation were more explicit about this, or maybe I've missed something.)
Probably the recommended way to print
unsigned char
values in hex would be:Note that this is specific to Keil C51, and makes your code non-portable to other platforms. But then again, code written to run on such a small system isn't likely to be portable anyway.
Casting to
unsigned int
, as I suggested previously, should also work, but using"%02bx"
might be more efficient in time and code size, since the arguments can be passed as single bytes.