Hello,
I think I have found a bug in the sprintf function of the C51 run-time library:
If I call sprintf to substitute an integer ("%d", "%u" or "%i") from a one byte variable sprintf will write garbage into the array.
Here a simple example:
unsigned char xdata testArray[5] = ""; // testArray = {0x00, 0x00, 0x00, 0x00, 0x00}
unsigned char xdata oneByteVariable = 1;
sprintf(testArray, "%d", oneByteVariable); // testArray = {0x32, 0x35, 0x36, 0x00, 0x00} ASCII: "256"
If I use instead of a one byte a two byte variable, sprintf will work correctly:
unsigned int xdata twoByteVariable = 1; sprintf(testArray, "%d", twoByteVariable); // testArray = {0x31, 0x00, 0x00, 0x00, 0x00} ASCII: "1"
I am using a silicon labs 8051 with the latest C51 development tools (version 9.55).
Can someone confirm this bug?
Best Regards, Andreas Hornsteiner
%d of sprintf(), without any size prefix, always expects the address of an "int" variable.
And Keil very clearly documents that the C51 compiler has a 16-bit, i.e. two-byte, int.
So if Keil did implement %d of sprintf to work on anything else but a two-byte int, then Keil would have a broken sprintf() that didn't comply with the C language standard.
If you look at the sprintf() documentation for other compilers, you would normally not see any mention of the number of bytes of the integer type for %d - since the size of int is locked down outside of the scope of the sprintf() function.
And just as sprintf() expects the address of an int when it processes %d, printf() expects the value of an int stored in the size of an int.
gcc for example documents:
The int argument is converted to signed decimal notation. The precision, if any, gives the minimum number of digits that must appear; if the converted value requires fewer digits, it is padded on the left with zeros. The default precision is 1. When 0 is printed with an explicit precision 0, the output is empty.
So the gcc documentation requires you to know if you are using a version of gcc with 16-bit, 32-bit or 64-bit int type.
The tricky part to remember with compiler manuals is that they are not - and are not expected to be - replacements for the C language standard.
No, that would be *scanf. The *printf() family's %d format takes an int, not the address of one.
But that's not even the issue here. The reason this code didn't work is that C51, by default, isn't C Standard compliant: it doesn't do the standard integer promotions, because that allows for more efficient compiled code.
A Standard C compiler would have promoted that char to int, on passing it as a variadic argument, and then sprintf() %d would have printed it as an ordinary int.
For C51 in its non-standard default mode, this promotion doesn't happen. So the char is passed as-is, and %d format would be wrong for that. That's what %bd is for.
So you have not just one, but three ways out of this:
1) turn on ANSI standard promotion rules 2) cast the char to (int) explicitly 3) use non-standard %bd format
Yes of course I intended to write scanf - I actually misread the original question to mean scanf and not printf. That's why I later mentioned printf as a side note.
For the standard integer promotion, it's enforced by most compilers since it's a requirement of the language standard, while C51 gives the user an option to go with compliance or with efficiency/size - all because the 8051 is badly suited for doing 16-bit arithmetic. Most other 8-bit processors have a better instruction set and/or register set for working on data larger than 8 bits.