Jump to content
 
  • 0

Sprintf acting strangely


Orunmila

Question

  • Member

I am seeing weird behavior when I am using sprintf with my code, the code looks fine but it is printing junk!

Here is an example program for a PIC16F18875 :

#include <xc.h>
#include <stdio.h>
#include <stdint.h>

char buffer[128]; 
void main(void) {
   
    uint32_t multiplier = 10;
    
    sprintf(buffer, "Value1 : %02d, Value2 : %02d, Value 3: %02d", 5*multiplier, 7*multiplier, 8*multiplier);
    
    NOP();
}

The resulting string printed is this:
 

Value1 : 50, Value2 : 00, Value 3: 70

Why is this happening?

 

Link to comment
Share on other sites

6 answers to this question

Recommended Posts

  • Member

A lot of people use printf and sprintf quite a lot without thinking how it actually works. The problem here is that the format string is interpreted at runtime, but the parameters are passed in with specific data types. 

If you look deeper into the specification for these functions you will find that the relationships between the data types in your format string (e.g. %d) to the parameters you are passing through after the format string is purely by agreement and the compiler cannot convert the variables to the correct type automatically at compile time.

The conversion rules for printf (and sprintf) say that every parameter passed into the function in the variable argument list (that is after the comma) shall be subjected to integer promotion, which means that it will be increased in size at least to an "int". The size of an int would be different depending on the architecture or device, but it will always be at least 16-bits (as the C standard requires the int type to be at least 16-bits - even if the processor is only 8-bits)

After this the parameters are packed next to each other in memory as a raw data block, and the printf code will unpack it again assuming that you followed the agreement of passing the correct data types.

What happened in the code in the question above is that the constant used to multiply with was a 32-bit value, which means that we passed 3x 32 bit values into printf. So the raw data block looked like this:

32-bit 32-bit 32-bit
0x00000032 0x00000046 0x00000050

But the sprintf function is expecting 3 x integers which are on this platform 16-bits in size, which means that it is interpreting the data as we specified which is 3x 16bit integers:

16-bit 16-bit 16-bit
0x0032 0x0000 0x0046

Of course the next integers would be 0x0000 followed by 0x0050 and then the last 0x0000. The order of these are such because of the order the bytes are stored within the number (little endian, which means the lowest byte has the lowest address).

If we used %ld instead the printf function would have interpreted the data correctly of course, so always make sure you use the correct format specifiers which match your parameters exactly, the compiler is not going to check this for you - it is your responsibility!

When you are on a 32-bit processor where an int is actually 32-bits it is very easy to get this right by accident as everything ends up being converted to a 32-bit parameter, but you should still take care. Sometimes we use larger data types and often our code needs to be ported to other devices and this can easily become a hard bug to track down if you are not aware of these mechanics.

 

 

Link to comment
Share on other sites

  • Member
9 hours ago, Orunmila said:

When you are on a 32-bit processor where an int is actually 32-bits it is very easy to get this right by accident as everything ends up being converted to a 32-bit parameter, but you should still take care. Sometimes we use larger data types and often our code needs to be ported to other devices and this can easily become a hard bug to track down if you are not aware of these mechanics.

I will ALWAYS try to keep code running on more than one platform even if the second platform is just a different compiler.  The second platform provides additional opportunities to catch mistakes like this one simply because different compilers or systems have different constraints.  In the past I would keep my code running on a Motorola 68000 and an Intel 80x86 simply because the endian differences would always reveal themselves and keeping the code portable kept silly math errors away.

Link to comment
Share on other sites

Not necessarily related to your problem but I always recommend (if your library provides it) to use snprintf() instead of sprintf(). The difference is that snprintf() takes an additional parameter, the size of the string buffer, and as a result of knowing the size of this buffer, it won't overrun it; whereas sprintf() has no idea what is the size of the buffer and may overrun it, hence you always see code using sprintf() passing in big buffers (you used 128 as the size of the buffer) to have lots of "extra space." Much better to use snprintf() and know that the buffer will never be overrun.

Link to comment
Share on other sites

  • Member
57 minutes ago, twelve12pm said:

Not necessarily related to your problem but I always recommend (if your library provides it) to use snprintf() instead of sprintf(). The difference is that snprintf() takes an additional parameter, the size of the string buffer, and as a result of knowing the size of this buffer, it won't overrun it; whereas sprintf() has no idea what is the size of the buffer and may overrun it, hence you always see code using sprintf() passing in big buffers (you used 128 as the size of the buffer) to have lots of "extra space." Much better to use snprintf() and know that the buffer will never be overrun.

I think it is related enough. It is always safer to use snprintf, and in fact many coding standards explicitly prohibits the use of the non-length checking versions of these functions. Unfortunately we have not been able to convince Microchip of the importance of this (yet) and today XC8 V2.0 does not support snprintf, strncpy or any of these safer versions, so if you are on a PIC using XC8 - alas, that is not an option for you 😞

 

Link to comment
Share on other sites

On 12/30/2018 at 1:10 PM, Orunmila said:

I think it is related enough. It is always safer to use snprintf, and in fact many coding standards explicitly prohibits the use of the non-length checking versions of these functions. Unfortunately we have not been able to convince Microchip of the importance of this (yet) and today XC8 V2.0 does not support snprintf, strncpy or any of these safer versions, so if you are on a PIC using XC8 - alas, that is not an option for you 😞

 

See http://www.elm-chan.org/fsw/strf/xprintf.html for one possible embedded implementation. I've seen others out there...

Link to comment
Share on other sites

  • Member
3 hours ago, twelve12pm said:

See http://www.elm-chan.org/fsw/strf/xprintf.html for one possible embedded implementation. I've seen others out there...

Hmmm, very interesting!

Do you have a link to one which actually implements snprintf as well? That would be very useful to have, since this one only implements the non-length checked versions I am not sure if I would really use this particular one. I also see that it does not implement floating point at all...

On XC8 the compiler actually does a very good job to link in only the parts of the number conversion which you are actually using in your format specifiers, so I think it will be pretty hard to beat that. That means that if you are not using %f anywhere you will not pay the enormous price to print those.

 

 

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

 


×
×
  • Create New...