Int to char (itoa) buffer adding garbage to next call

Narrative explanation:
My actually code is huge & involves several classes, but I've managed to isolate the problem to itoa(). I call this many times to convert integers to char arrays that can be printed out. All the arrays in question are of length=4. It seems that when I run a 4 digit number through there & then a lesser digit number (say 1 digit), itoa adds characters thats shouldn't be there. From what I've seen, the characters it adds are values in the previous itoa call...leading me to think there is a buffer not being cleared...but I cant find the actual code for itoa to see whats happening there.

Code explanation:
here is the code example of what I'm talking about, I'm just running this in setup as a test:

   uint32_t testvar=0;
   char testArr[4];
   itoa(testvar, testArr, 10);
   Serial << testvar << F("\t");
   for(uint i=0;i<4; i++)            Serial  <<testArr[i] << F(" ");
   Serial << endl;

running that prints: (as expected)

0       0 ␀ ␀ ␀  

but if I add a call to itoa() with a 4 digit number & then run it again with 0:

   uint32_t testvar=5632;
   char testArr[4];
   itoa(testvar, testArr, 10);
   testvar=0;
   itoa(testvar, testArr, 10);
   Serial << testvar << F("\t");
   for(uint i=0;i<4; i++)            Serial  <<testArr[i] << F(" ");
   Serial << endl;

running that prints:

0       0 ␀ 3 2  

notice how the "3" & "2" come from the previous call.

So, would you all agree that is the problem? What are my options as a work around (im drawing a blank)?

**shouldn't matter, but this is for an esp32. coding in platformIO

testArr[] needs to be big enough to hold the value AND the null terminator for the string.

Try with testArr[5].

2 Likes

testArr is too small. There needs to be space for the null terminator.

1 Like

nah, same thing happens if I pass a 3 digit number. and I did try length=5, just for fun. same result.

i did one more test to narrow it down:
I wanted to know if the prev data was sticking in the itoa buffer or in my array, so I wrote to 2 different arrays:

 uint32_t testvar=5632;
   char testArr[5];
   char testArr2[5];
   itoa(testvar, testArr2, 10);
   testvar=0;
   itoa(testvar, testArr, 10);
   
   Serial << testvar << F("\t");
   for(uint i=0;i<4; i++)            Serial  <<testArr[i] << F(" ");
   Serial << endl;

from that I can conclude that itoa() does not write null fields to the array, thus the old values are not getting overwritten.

should be able to just zero the array before the itoa call, that should do it.

itoa() does indeed write the zero terminator. Not sure where your mistake is.

From the C++ reference:

<stdlib.h>

itoa

char * itoa ( int value, char * str, int base );

Convert integer to string (non-standard function)

Converts an integer value to a null-terminated string using the specified base and stores the result in the array given by str parameter.

If base is 10 and value is negative, the resulting string is preceded with a minus sign (-). With any other base, value is always considered unsigned.

str should be an array long enough to contain any possible value: (sizeof(int)*8+1) for radix=2, i.e. 17 bytes in 16-bits platforms and 33 in 32-bits platforms.

I should have phrased that better, itoa only writes a single null character. so when my numbers went from 4 digits, to 1 digit (0), itoa would write a 0 to [0], a null to [1] (the terminator) and then leave whatever was in 2 & 3 untouched. at least thats what I am seeing. and I seem to have fixed it simply by initiated the array to 0, which is created right before being used each time.

Somehow, Serial << testvar; seems to output all characters in testvar on your screen. Including '\0' and beyond....
What happens if you use Serial.println(testvar);?

those are exactly equivalent calls & testvar is just an integer so theres no other numbers.

the char array comes from the serial call in the for loop directly below.

I think I got it worked out, thanks though!

Looking into this a bit more:
Itoa keeps the buffer and overwrites it with your new info.
If your new info has less characters, not all old info is overwritten.
Normally this is no problem as you are supposed to print the string until the '\0' character, not beyond there (like you do in the for loop).

look into what? thats exactly what I said? I had just made the incorrect assumption that if there were fewer digits, it set everything past the null term, to null. lesson learned.

and the serial print was simply a diagnostic tool, the array updates digits(1 at a time) on a vga screen. my application doesn't call for a null terminator in the string.

i'll read an array any way I like, thank you very much :wink:

appreciate you giving it some thought!

What I meant to say is that you are using itoa in a way that it was not designed for.
You are fully free to do so and to find your own ways to work around the troubles it causes.
So: I think you did a good job on analysing your problem yourself and on finding a workaround!
But: I am almost sure that alternatives for itoa exist that would not need the work around.
I also wanted to point out that itoa is doing nothing wrong here. It is just not doing what you want it to do...

1 Like

why do you keep repeating things I've already said? #rhetorical

and my work around is not even a work around, its just how I would have coded it in the first place. nothing lost there.

Take care.

The terminator marks the end of the string, you're supposed to STOP then. If you do, you don't need extra code to prefill string space with zeroes. Do you see that?

i reject the word "suppose" to, but yah I get I'm a bit off label. and the extra code, was just initializing the array to 0, no biggy. code works fine. thanks.

I have done fixed-length text work before, and made it work though first in BASIC using the MIDSTRING command and then in C using strncpy() that does not insert a terminating null since it is for changing text inside of a string.

I don't agree that you're going about this right but if you read the array as packed data then the usual course is to write a loop that extracts digits from a binary value by dividing by ten and putting the remainder as ASCII into the right array spot from low to high means right to left (1234 = 4 1's, 3 10's, etc.

The itoa() function is made of the same code you write, optimized by the same compiler,

strncpy() is more complicated than that.

     The stpncpy() and strncpy() functions copy at most len characters from
     src into dst.  If src is less than len characters long, the remainder of
     dst is filled with `\0' characters.  Otherwise, dst is not terminated.

(this is what OP wants, though.)
(and it CAN be used for changing text inside a string, as well.)
(but it is NOT a "safe strcpy()", which is what a lot of people seem to expect. Probably you should use strlcpy() for that.)

Point it to the 1st char, make n the length of the buffer - 1. What happens with source shroter, equal to or longer than n?

Which? Like this?

source: "testing" (length 7, or 8)

dest size [5] [7] [8] [10]
strcat testing\01 testing\01 testing\0 testing\0xx
strncat(...,sizeof(dest)) testi2 testing2 testing\0 testing\0\0\0
strlcat(...,sizeof(dest)) test\0 testin\0 testing\0 testing\0xx

x: unknown or not modified.
1: buffer overflow on copy.
2: dest not null-terminated. potential overflow/etc on attempt to use dest as C string.

Also, never, ever do strncpy(dest, source, strlen(source)) (or with strlcpy)
(You'll probably look at it and say "of course not! That's Silly!", but I've seen it quite a lot, even in professional environments. It's essentially what happens when you pass an edict that strcpy() should never be used, and people "fix" their code without noticing the actual problem being addressed.)

Also, never, ever do `strncpy(dest, source, strlen(source))

source = "1234" --- strlen = 4

AVR-LibC:
Thus, if there is no null byte among the first n bytes of src, the result will not be null-terminated.

If dest is 4 chars into "ABCDEFGHIJKLM", the never do should make dest "ABC1234HIJKLM" because up to n chars will be copied without a NULL.

And yes, it's up to me to keep dest from getting written past.

It has pretty much the same failure modes as the plain strcpy() - if the destination is long enough it works fine. If the destination isn't long enough, it breaks stuff badly.
You don't know anything about the length of the destination by checking the length of the source.

1 Like