Jump to content
 
  • 0
Orunmila

Sprintf acting strangely

Question

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?

 

Share this post


Link to post
Share on other sites

6 answers to this question

Recommended Posts

  • 1

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.

 

 

  • Helpful 1

Share this post


Link to post
Share on other sites
  • 0
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.

Share this post


Link to post
Share on other sites
  • 0

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.

Share this post


Link to post
Share on other sites
  • 0
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 😞

 

Share this post


Link to post
Share on other sites
  • 0
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...

  • Helpful 1

Share this post


Link to post
Share on other sites
  • 0
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.

 

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


 


  • Popular Contributors

    Nobody has received reputation this week.

  • Similar Content

    • By Orunmila
      error: variable has incomplete type 'void'
      If you are getting this error message trying to compile code that used to work or from some example code you found somewhere it is very likely because of the changes in the XC8 interrupt syntax introduced with V2.0 of XC8.
      Specifically I am getting this today for my interrupt service routine definition. I used to use the age-old way of doing this in XC8 as follows:
      // Old way to do this void interrupt myISR(void) { // Old way Interrupt code here } After the changes to the XC8 compiler which were mostly motivated to get better C standard compliance, particularly with C99, the syntax for that function should now use the commonly adopted concept of function declaration-specifier attributes, which traditionally start with 2 underscores and contains either a list of parameters in brackets or have empty brackets if no parameters are present. 
      // New and improved and C99 compliant way to specify an interrupt service routine in XC8 void __interrupt() myISR(void) { // New and improved interrupt code here } This syntax is now also consistent between XC8, XC16 and XC32
      Please see this post for more information on how to either work around this or change to the new syntax.
      https://www.microforum.cc/topic/5-i-used-to-use-to-locate-variables-but-since-xc8-20-this-is-no-longer-working/
    • By Orunmila
      A colleague of mine recommended this little book to me sometime last year. I have been referring to it so often now that I think we should add this to our reading list for embedded software engineers.
      The book is called "Don't make me think", by Steve Krug.The one I linked below is the "Revisited" version, which is the updated version.

      This book explains the essense of good user interface design, but why would I recommend this to embedded software engineers? After all embedded devices seldom have rich graphical GUI's and this book seems to be about building websites?
      It turns out that all the principles that makes a website easy to read, that makes for an awesome website in other words, apply almost verbatim to writing readable/maintainable code! 
      You see code is written for humans to read and maintain, not for machines (machines prefer to read assembly or machine code in binary after all!). The principles explained in this book, when applied to your software will make it a pleasure to read, and effortless to maintain, because it will clearly communicate it's message without the unnecessary clutter and noise that we usually find in source code.
      You will learn that people who are maintaining and extending your code will not be reasoning as much as they will be satisficing (yes that is a real word !). This forms the basis of what Bob Martin calls "Viscosity" in your code. (read about it in his excellent paper entitled Design Principles and Design Patterns. The idea of Viscosity is that developers will satisfice when maintaining or extending the code, which results in the easiest way to do things being followed most often, so if the easiest thing is the correct thing the code will not rot over time, on the other hand if doing the "right" thing is hard people will bypass the design with ugly hacks and the code will become a tangled mess fairly quickly. But I digress, this book will help you understand the underlying reasons for this and a host of other problems.
      This also made me think of some excellent videos I keep on sending to people, this excellent talk by Chandler Carruth  which explains that, just like Krug explains in this little book, programmers do not actually read code, they scan it, which is why consistency of form is so important (coding standards). Also this great talk by Kevlin Henney which explains concepts like signal to noise ratio and other details about style in your code (including how to write code with formatting which is refactoring immune - hint you should not be using tabs - because of course only a moron would use tabs)
      Remember, your code is the user interface to your program for maintainers of the code who it was written for in the first place. Let's make sure they understand what the hell it is you were doing before they break your code!
      For the lazy - here is an Amazon share link to the book, click it, buy it right now!
      https://amzn.to/2ZEoO4O
       
    • By Orunmila
      I came across this one yet again today, so I wanted to record it here where people can find it and where I can point to it without looking up all the details in the standard over and over again.
      I know pointers are hard enough to grok as it is, but it seems that const-qualified pointers and pointers to const-qualified types confuses the hell out of everybody.
      Here is a bit of wisdom from the C standard. As they say - if all else fails read the manual!
      BTW. The same applies to all qualifiers so this counts for volatile as well. I see the mistake too often where people are trying to make a pointer which is being changed from an ISR e.g. and they will use something like:
      volatile List_t * ListHead; This usually does not have the intended meaning of course. The volatile qualifier applies to the List_t and not to the pointer. So this is in fact not a volatile pointer to a List_t. It is instead a non-volatile pointer to a "volatile List_t". In simple terms it is the variable at the address being pointed to which is volatile, not the address itself (the pointer). 
      To make the a volatile pointer, that is a pointer which is changed from another context such as an ISR, you need to do it like this:
      List_t * volatile ListHead; Of course if both the pointer and the thing it is pointing to are volatile we do it like this:
      volatile List_t * volatile ListHead;  
      There is another example in section 6.2.5 of the standard.
       
    • By Orunmila
      I just downloaded XC32 V2.15, I was using V2.10 before. I find that some of my projects no longer compile. On my first check I noticed that the problems seem to occur when inline functions are used and the same header where the inline implementation is done is included in more than one compilation unit?
      Has any of you seen similar issues?
      I will investigate further and post here if I arrive at an answer.
       
      UPDATE: Ok, I managed to make a small test project to replicate the problem. I am attaching it here. 
      TestInlineXC32_2.15.zip
       
      Next I am going to test this on some other compilers to see what the deal is. I have confirmed that with that project when you switch it to V2.10 or older it all compiles just fine, but if you use V2.15 it failes to link with the following error:
      "/Applications/microchip/xc32/v2.15/bin/xc32-gcc"   -mprocessor=32MZ2048EFM100  -o dist/default/production/TestInlineXC32_2.15.X.production.elf build/default/production/main.o build/default/production/otherFile.o          -DXPRJ_default=default  -legacy-libc    -Wl,--defsym=__MPLAB_BUILD=1,--no-code-in-dinit,--no-dinit-in-serial-mem,-Map="dist/default/production/TestInlineXC32_2.15.X.production.map",--memorysummary,dist/default/production/memoryfile.xml
      nbproject/Makefile-default.mk:151: recipe for target 'dist/default/production/TestInlineXC32_2.15.X.production.hex' failed
      make[2]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
      nbproject/Makefile-default.mk:90: recipe for target '.build-conf' failed
      make[1]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
      nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed
      build/default/production/otherFile.o: In function `myInlineFunction':
      /Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X/inlinedheader.h:6: multiple definition of `myInlineFunction'
      build/default/production/main.o:/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X/inlinedheader.h:6: first defined here
      /Applications/microchip/xc32/v2.15/bin/bin/gcc/pic32mx/4.8.3/../../../../bin/pic32m-ld: Link terminated due to previous error(s).
      collect2: error: ld returned 255 exit status
      make[2]: *** [dist/default/production/TestInlineXC32_2.15.X.production.hex] Error 255
      make[1]: *** [.build-conf] Error 2
      make: *** [.build-impl] Error 2
      BUILD FAILED (exit value 2, total time: 680ms)
       
       
       
    • By Orunmila
      If you have purchased a "MPLAB(R) Xpress PIC18F47K40 Evaluation Board" from Microchip (part number DM182027) and you are running into difficulty because the board is behaving strangely it is most likely caused by a silicon errata on this device!
      The errata can be downloaded here: http://ww1.microchip.com/downloads/en/DeviceDoc/PIC18F27-47K40-Silicon-Errata-and-Data-Sheet-Clarification-80000713E.pdf
      The relevant section of the Errata is shown at the end.
      What is happening is that the compiler is using a TBLRD instruction somewhere and this instruction is not behaving as expected due to a silicon bug in REV A2 of the PIC18F47K40, causing the read to fail and the program to malfunction. Typically this happens as part of the C initialization code generated by the XC8 compiler, and since the compiler is optimizing, changing the code may cause the problem to temporarily disappear because you have few enough global variables that a table read is no longer the fastest way to initialize the memory segment for variables with static linkage.
      The XC8 compiler can avoid generating the sequence which will cause the failure if you tell it in the linker settings to implement the workaround for this Errata. This is done by adding +NVMREG to the setting as follows. Note that this is under the section "XC8 Linker" and the Option Category "Additional Options".
       

       
      This is the relevant section of the Errata.

       
       
    • By KM1
      Hello,
      I have run into a strange (for me) issue with the rtcounter module as provided by MCC and shown by the example program in the blog area of this site. I am using MPLabX and XC8, both up-to-date versions and I am trying the example on a pic18F47k40 xpress board. The issue is I set an output (RA4) in main after initializing the pic and then enter the while(1) loop where the rtcount_callNextCallback(); is called. The output now is turning on and off. The off duration is typically ~60u seconds, and on time can be varied with changes to the timer interrupt settings (I use a 1mS timer0 in 16 bit mode) and the number sent to the callback. Approximately every 3mS, the off time increases such that that cycle (only) is approx 50% duty cycle.
      Turning the output on or off in the callback routine has no effect - I initially wanted to toggle on and off at a speed visible to the eye. Commenting out the callback in the main loop stops the output from going low.
      I have checked errata and used the data sheet to confirm register settings, I tried moving the current-limited LED from RA4 to RA2, and I have studied the example program looking for obvious differences that may cause this behaviour.
      I would appreciate thoughts and/or suggestions.
      Keith
         
    • By Orunmila
      I have a bunch of old projects using the @ method to hard locate variables at a specific location in data memory, but since I upgraded to XC8 2.0 none of these are working any more!
      How do I do this with the latest compiler? I am using XC8 v2.xx.
      I also saw this error at my interrupt service routine and I suspect that this is the same problem.
      My interrupt code looks like this 
      void interrupt my_isr(void) { ... my code here } error: variable has incomplete type 'void'
×
×
  • Create New...