1

I'm having an error in buffers that handle strings and I can not figure out what the problem is. The example below shows what is happening. Can someone help me?

void setup() {
   char* ss1 = "";
   char* ss2 = "";
   String txt = "";

   Serial.begin(115200);

   txt = "1234560001a";
   txt.toCharArray(ss1, txt.length());

   Serial.printf("\n\n");
   Serial.printf("ss1 = %s\n", ss1); // It prints "1234560001" whithout last char "a"      

   // Force incorrectly "ss1" to be equal to "ss2" and still removes the last character.
   txt = "abcdef";
   txt.toCharArray(ss2, txt.length());

   Serial.printf("ss1 = %s\n", ss1); // It prints "abcde" whithout last char "f"
   Serial.printf("ss2 = %s\n", ss2); // It prints "abcde" too
}
9
  • see what happens if you make a change in this line ... txt.toCharArray(ss1, txt.length()); .... think about it – jsotola Jul 8 '18 at 2:53
  • What kind of change do you mean? There are two problems occurring in the code: 1) the string is removing the last character when copying; 2) The contents of the second variable are overwriting the contents of the first variable. – wBB Jul 8 '18 at 2:57
  • I understand that you meant that It have problem in length() and that doing length() + 1 works. if string = 'abc', then string.length () should be 3, isn't it? But I insist: So is this an IDE problem? About the second problem, the pointers, why are they overwriting the contents? – wBB Jul 8 '18 at 3:11
  • strings are null terminated .... the char array must have space for that null – jsotola Jul 8 '18 at 3:59
  • i am not sure about the ss1 and ss2 pointers ..... maybe, because the ss1 and ss2 are created without a size, ss2 points to memory ss1+0 – jsotola Jul 8 '18 at 4:04
1

toCharArray is alias for getBytes. getBytes is documented as "Copies the String’s characters to the supplied buffer." You did not allocate the memory for the copied characters.

the right use of 'getBytes' and 'toCharArray' is

char buffer[BUFF_SIZE];
str.getBytes(buffer, sizeof[buffer]);

note: getBytes functions sets the terminating zero of the c-string in buffer

0
1

About the problem with losing the last character:

As already said in the comments, the toCharArray() method is only an alias for the getBytes() method of string objects. We can see it's implementation in the IDEs folder under hardware/arduino/avr/cores/arduino/WString.cpp:

void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const
{
    if (!bufsize || !buf) return;
    if (index >= len) {
        buf[0] = 0;
        return;
    }
    unsigned int n = bufsize - 1;
    if (n > len - index) n = len - index;
    strncpy((char *)buf, buffer + index, n);
    buf[n] = 0;
}

We give the length as parameter, which is then reduced by 1. The function strncpy(), that is used here, will only copy n = bufsize - 1 bytes. In the end, the nth byte will be set to 0 as the terminating null character.

This means, that the length, that we give as parameter, already includes the space for the null character. But the method String::length() will not include the null character in length calculation (in most cases this is not wanted). So you can simply add 1 to the length before giving it to toCharArray().

About the overwriting problem:

As stated in this question's answer and in several other discussions about C/C++ (for example here), string literals are meant to be immutable. The mentioned sources say, it is different, if you define a string variable as

char *str = "test";

or

char str[] = "test";

In the second case a normal mutable array is created. In the first case the string will be placed in read only memory. From your problem I read, that on Arduino this is placed in RAM, thus technically mutable. But the compiler thinks of it as immutable. A common optimization, when dealing with immutable strings, is to let every used string literal with the same content be saved at the same memory address.

In your code you are initializing 2 strings with only the terminating null character in them. The compiler thinks "Oh, wait, these two string literals are immutable and equal, so I can save memory by only saving it once and let both pointers point to this memory address". When writing to the strings in Arduino the behavior doesn't seem to be defined. As the Arduino cannot throw an exception it will most likely do the write. Since both pointers are pointing to the same memory address, they show both the same content. Also, when writing a string longer than the initialized value, memory space outside from the original string will be written. If other variables are saved there, they will get overwritten. This is the reason, by you should always explicitely allocate the memory (static or dynamic) for a string, until another function does it for you. When using a dynamic allocation of memory, you should also consider heap fragmentation, which can easily lead to strange behavior.

If you turn on verbose output for compilation, the compiler also warns you, that you try to convert a constant string literal to a non-constant char pointer:

sketch_jul09a.ino:3:15: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

To conclude, you should not declare string variables like this. If you have a variable string buffer with initialized values, you should use

char str[]="test";

If you want to define a constant string, use the keyword const:

const char *str = "test";

Then writing to it will result in a compiler error, so that you sense, if you are doing this.

1
  • Thank you for your tips. I have not yet solved the second problem either, but I'm using a palliative solution (even against my own will, for lack of a better option). I'm using a fixed length buffer and copying the string with getBytes as pointed out by @Juraj. Thanks. – wBB Jul 9 '18 at 1:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.