How to avoid compiler warning ( long into sprintf %02d)?

Here's the relevant code snippet:


uint32_t tmp_x;
      
(some code that sets the value of tmp_x.....)      

          tmp_x = ( ((tmp_x > 99) ? 99 : tmp_x) ); // Limit tmp_x to max val 99

          sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x); // add xx to string
          // compiler gives warning on above, tmp_x int32 into a %2d
          // but since I limit it to 99 value max, it is OK to ignore warning.

So as I note, the compiler throws a warning since the sprintf is attempting to fit a 32 bit INT into a 2 place value. But since I limit it to 99, this should not be a problem.

Even if I were to cast this, an 8 bit INT could be 3 places.

I've been ignoring the warning, since it is a warning and not a hard stop, but it would be nice to have a clean output when I compile.

Maybe convert to a char string, and trim that to two char (not sure of the commands to use for that)?

sprintf needs to know the variable type:

          sprintf(Text_tmp, "%s%02lu", Text_tmp, tmp_x); // add xx to string

I keep this reference link handy: https://www.cplusplus.com/reference/cstdio/printf/

Is there a good reason for using an unsigned long integer to store a number in the range of 0 to 99, when a byte would do?

Well, I use that variable for intermediate long calculations involving millis(). But then that gets divided down to display (on a 4x20 LCD) things like hours, days, weeks, years.

But as I alluded, even if I used a byte, that could have a 3 place value of 255, and would be the same compiler error. And I just tried it - same error.

I'll look into your ""%s%02lu"" suggestion, I'll need to read up on that, I don't know what it does offhand - thanks.

The revised code is wrong. Post it, using code tags.

I'll need to read up on that, I don't know what it does offhand - thanks.

Is it too much trouble to click on the link I posted, which gives all the details?

Is it too much trouble to click on the link I posted, which gives all the details?

No, not at all - I just wanted to give some feedback while I went to read up on it, in case I got called away from it - thanks.

sprintf(Text_tmp, "%s%02lu", Text_tmp, tmp_x); // add xx to string

I also increased the size of char Text_tmp[120], just in case. And, instead of uint32_t, I also changed to the Arduino format of unsigned long for tmp_x, and I still get this compiler error:

warning: '%02lu' directive writing 2 bytes into a region of size between 1 and 120 [-Wformat-overflow=]

Post the newly erroneous code. ALL of it.

OK, I created a sketch to keep this to the minimum to demo the warning, Line 31 seems to work OK with the:

sprintf(Text_tmp, "%02lu", tmp_x); // Line 31

But adding the %s throws warnings? (Lines 22 and 39) - but that's a different warning than the "int" vs "long unsigned int".

unsigned long tmp_x = 123456;
char Text_tmp  [120] = "HOURS: ";
char Text_tmp_2[120] = "";

void setup()
{
  // put your setup code here, to run once:
}

void loop()
{
  tmp_x = ( ((tmp_x > 99) ? 99 : tmp_x) );

  sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x); // Line 14
  // compiler gives warning on above, tmp_x int32 into a %2d
  // but since I limit it to 99 value max, it is OK to ignore warning.

  Serial.println(Text_tmp);

  // Try this:

  sprintf(Text_tmp, "%s%02lu", Text_tmp, tmp_x); // Line 22

  // also get compiler warnings

  Serial.println(Text_tmp);


  // Try this - without the %s prefix:

  sprintf(Text_tmp, "%02lu", tmp_x); // Line 31

  // no warning w.o the %s???

  Serial.println(Text_tmp);

  // Try a different char string in source/dest????

   sprintf(Text_tmp, "%s%02lu", Text_tmp_2, tmp_x); // Line 39


  Serial.println(Text_tmp);
}

I added LIne #'s in the comment to help match up with the compiler warnings:

...sketch_apr20a.ino: In function 'void loop()':
...sketch_apr20a.ino:14:21: warning: format '%d' expects argument of type 'int', but argument 4 has type 'long unsigned int' [-Wformat=]
   14 |   sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x); // Line 14
      |                     ^~~~~~~~            ~~~~~
      |                                         |
      |                                         long unsigned int
...sketch_apr20a.ino:14:21: warning: '%02d' directive writing 2 bytes into a region of size between 1 and 120 [-Wformat-overflow=]
   14 |   sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x); // Line 14
      |                     ^~~~~~~~
...sketch_apr20a.ino:14:21: note: directive argument in the range [0, 99]
...sketch_apr20a.ino:14:10: note: 'sprintf' output between 3 and 122 bytes into a destination of size 120
   14 |   sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x); // Line 14
      |   ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...sketch_apr20a.ino:14:10: warning: 'sprintf' argument 3 overlaps destination object 'Text_tmp' [-Wrestrict]
...sketch_apr20a.ino:2:6: note: destination object referenced by 'restrict'-qualified argument 1 was declared here
    2 | char Text_tmp  [120] = "HOURS: ";
      |      ^~~~~~~~
...sketch_apr20a.ino:22:21: warning: '%02lu' directive writing between 2 and 10 bytes into a region of size between 1 and 120 [-Wformat-overflow=]
   22 |   sprintf(Text_tmp, "%s%02lu", Text_tmp, tmp_x); // Line 22
      |                     ^~~~~~~~~
...sketch_apr20a.ino:22:10: note: 'sprintf' output between 3 and 130 bytes into a destination of size 120
   22 |   sprintf(Text_tmp, "%s%02lu", Text_tmp, tmp_x); // Line 22
      |   ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...sketch_apr20a.ino:22:10: warning: 'sprintf' argument 3 overlaps destination object 'Text_tmp' [-Wrestrict]
...sketch_apr20a.ino:2:6: note: destination object referenced by 'restrict'-qualified argument 1 was declared here
    2 | char Text_tmp  [120] = "HOURS: ";
      |      ^~~~~~~~
...sketch_apr20a.ino:39:22: warning: '%02lu' directive writing between 2 and 10 bytes into a region of size between 1 and 120 [-Wformat-overflow=]
   39 |    sprintf(Text_tmp, "%s%02lu", Text_tmp_2, tmp_x); // Line 39
      |                      ^~~~~~~~~
...sketch_apr20a.ino:39:11: note: 'sprintf' output between 3 and 130 bytes into a destination of size 120
   39 |    sprintf(Text_tmp, "%s%02lu", Text_tmp_2, tmp_x); // Line 39
      |    ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Never ignore a warning should be rule #1

The compiler tells you it will fetch only 2 bytes in memory (an int size) and you passed something that fits on 4 bytes. Because the architecture is little endian that works - the two bytes fetched are fine and will hold your value but that just being lucky not because you constrained the value to be small enough.

Don’t be lazy and hope all will work out - Use the right format for your variables and your coding practice will be better.

—-

The other issue is that you can’t use sprintf with overlapping data (ie the same buffer as output and an input parameter). You need a temporary string and use strcat()

From the glibc documentation

The behavior of this function is undefined if copying takes place between objects that overlap—for example, if s is also given as an argument to be printed under control of the ‘%s’ conversion. See Copying Strings and Arrays.

Do not use uint32_t together with sprintf(). That is not allowed. That means that sprintf() behaves different on platforms with different int sizes, and that is indeed how it is.
It is not possible to write platform-independent code with sprintf().

For the Arduino Uno there is a warning for using %02d with unsigned long as parameter, that is indeed wrong.
The same warning is generated with "Raspberry Pi Pico", "Arduino Nano Every", "ESP32", and so on.

With "ESP8266 / Generic ESP8266" I get those other warnings.
So you have a ESP8266 ?

The other warnings are generated because the compiler tries to be (too) smart and your code is not clean enough. Write better code.

This is no good enough:

char Text_tmp  [120] = "HOURS: ";
unsigned long tmp_x = 123456;
sprintf(Text_tmp, "%s%02d", Text_tmp, tmp_x);

If you make the parameter a "int" then the compiler sees that it might not fit in a "%02d", you can fix that.
The array is already filled with "HOURS: ", and is used both as parameter and destination. Please don't do that. The destination might be created on the fly.
I never tried what would happen with the same array as parameter and output. I don't want to try that, it is not good code.

Good code:

char Text_tmp  [120] = "HOURS: ";
unsigned long tmp_x = 123456;

int myLittleFlamingo = (int) tmp_x & 0xFF;
char myBigBlueWhale[130];
sprintf(myBigBlueWhale, "%s%02d", Text_tmp, myLittleFlamingo); // Line 14

Better code:

const char Text_tmp[] = "HOURS: ";

int myLittleFlamingo = 12;
char myBigBlueWhale[120];
sprintf(myBigBlueWhale, "%s%02d", Text_tmp, myLittleFlamingo); // Line 14

The "best" code uses snprintf(). Perhaps the return value can be used to build a whole string in a single char array.

or less flash memory hungry

void setup() {
  Serial.begin(115200);
  const char * Text_tmp = "HOURS: ";
  unsigned long tmp_x = 123456;

  char myBigBlueWhale[50]; // make sure it's large enough
  strcpy(myBigBlueWhale, Text_tmp);
  Serial.println(myBigBlueWhale);
  ultoa(tmp_x, memchr(myBigBlueWhale, '\0', sizeof myBigBlueWhale), 10);
  Serial.println(myBigBlueWhale);
}

void loop() {}

Thanks to all for the above info, this is making more sense to me now. I had thought about a command like " ultoa", but hadn't gone that far yet.

And yes, this is ESP8266 (NodeMCU 1.0)

I also think I tried not using the same char string for a source and destination in the sprintf, and still got errors, but that was probably in combination with the %02d parameter. I will make that change as well, throughout my program.

I may need to be away from this for a bit, but hopefully later today, I will report back when I have some results on the above suggestions.

Thanks again!

I like this... Nothing wrong with it. My style is only slightly different.

unsigned long tmp_x = 123456;// global unsigned long
char Text_tmp  [120];// global char array initialized to all nulls
//somewhere in the body of executed code...
snprintf(Text_tmp, sizeof Text_tmp, "%s%02lu", "HOURS: ", tmp_x);
// or alternatively
snprintf(Text_tmp, sizeof Text_tmp, "HOURS: %02lu", tmp_x);

The size parameter prevents the concatenated string from overrunning the size of the array (terminating NULL is included).

By the way, the variable names are terrible. use more descriptive names.

Uhm... which ones ?
In my opinion the "Text_tmp" and "tmp_x" names are terrible, so I made them something that can be visualized and remembered well. Do you see the little flamingo with a number tag with the hours which is put into the big blue whale ? I can.

Text_tmp and tmp_x

Your example used creative names. I kept the names from the OP but noted they were not descriptive.

Agreed. But in the actual program these are very local, and I use names like that for temporary intermediate use. I do use descriptive names for global and more widely used locals. And I keep a spreadsheet as I'm developing the program, with more detail. That's also commented in the program, but I like having the spreadsheet format as well, with other program notes.

I don't think anyone has a problem with using "i" or "x" and "y" in a tight 'for next' loop? But those are awful variable names if you need to do a search/replace. I kind of view these tmp_x and Text_tmp like i, x, y. If they only span a few lines, it's a lot different than having that name used throughout a program. Hah, but now that you mention it, I'm inconsistent, Text_tmp and X_tmp would be more consistent.