2

I'm using the Keil Cx51 compiler on an 8-bit EFM8 MCU.

If I have a function, foo(), like this:

xdata char buf[32];

void foo(char *msg, ...) 
{
    va_list args;
    va_start (args, msg);
    vsprintf(buf, msg, args);
    va_end(args);
}

And I use it like this:

foo("My number is %d", 1);

buf will contain: "My number is 256".

If I change the call to:

foo("My number is %d", (uint16_t)1);

Then buf will contain "My number is 1", as expected.

Why is vsprintf() bit-shifting the number 1 (0x0001) left by 8 bits to 256 (0x0100) without a cast? Is this an endianness issue?

Ed King
  • 1,833
  • 1
  • 15
  • 32
  • Looks more like an integer promotion issue. Keil does [have a web page](http://www.keil.com/support/docs/1754.htm) about it. – Hans Passant Feb 08 '18 at 15:58
  • Thanks for that link. I just disabled integer promotion as per the instructions and retested - the issue still remains. I am using Cx51 v9.53. – Ed King Feb 08 '18 at 16:10
  • 1
    That's backwards, you want it enabled. Do consider posting the assembly code. – Hans Passant Feb 08 '18 at 16:14
  • It's enabled by default. Please see attached assembly for the case where it's enabled. – Ed King Feb 08 '18 at 16:22
  • Q: What is the data type of the expression, `1` in Keil C? And, what data type does `%d` expect? In the C programming language, the answer to both questions is `int`; but Keil C is not C. It's been too long since I wrote code for an 8052, but if I had to guess, I'd guess that the data type of `1` might be `char`. – Solomon Slow Feb 08 '18 at 16:27
  • @jameslarge If `%d` expects an `int`, and I pass it a `char` of `1`, wouldn't it still be `1` as an `int`? This is why I thought it might be an endianness issue, as I could see that being a problem if I passed it a `char`, it turned it into a 16-bit `int` and `vsprintf()` read it in the 'other' endianness?. – Ed King Feb 08 '18 at 16:38
  • It's been ten years since the last time I used Keil C. I can't answer those questions. I can only suggest that _you_ ask them. I would not assume that Keil C obeys any particular rule that standard C obeys. – Solomon Slow Feb 08 '18 at 16:45

1 Answers1

2

It is a known problem that the data types of arguments in a functions having a "..." argument list matter because the compiler is normally not able to automatically adjust the data types.

Maybe some compilers will automatically detect the format specifiers in printf and adjust the data types in the arguments accordingly. GNU C for example will parse the string and print a warning message that the data types do not match but it does not adjust the data types.

If you don't use the correct data type you'll definitely get bad results.

Obviously %d means 16 bit for your compiler but 1 is some other data type (e.g. 8 bit).

Maybe with your compiler an argument list of the following form:

(uint8)a, (uint16)b, (uint8)c, (uint8)d

May be stored in the memory like this:

a, high_byte_of(b), low_byte_of(b), c, d

If the function expects c to be a 16-bit value the function will interpret the c in the memory as the high 8 bits of c and it will interpret d in the argument list as low 8 bits of d...

So in your case if you do the following:

foo("%d, %d", 1, 2, 3, 4);

The memory would look like this:

1, 2, 3, 4, ...

... and vsprintf will interpret this as 0x102 and 0x304, not as 1 and 2.

Of course in the RAM the 4 is followed by some other bytes which are not related to this part of the program so the RAM content maybe looks like this:

1, 2, 3, 4, 19, 32, 54, 21, 32, ...

In your case the first byte in the RAM would be a 1 followed by some "random" data (data not related to the function call).

Obviously the first byte after the 1 is a 0 so the vsprintf function interprets this as 0x0100.

If you use (uint16)1 as arguments the byte 0 followed by 1 will be written to the RAM so vsprintf interprets this as 0x0001.

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38