5

I've been searching for ways of emptying a char array in C/C++. I have come up with this code:

char testName[20];   

for(int i = 0; i < sizeof(testName); ++i)
{
  testName[i] = (char)0;
}  

It has been working for a while now but when I try to strlenthe result is always two more than the typed in word. For instance I input the word dog the output would be five. Why is that so? Is my char array not cleared?

Here is my code:

char testName[20];
void loop()
{
  if(Serial.available())
  {
    Serial.println("Waiting for name...");
    index = 0;
    for(int i = 0; i < sizeof(testName); ++i)
    {
        testName[i] = (char)0;
    }
    while(Serial.available())
    {
      char character = Serial.read();
      testName[index] = character;
      index++;
    }
    Serial.print("Name received: ");
    Serial.println(testName);
    Serial.print("The sentence entered is ");
    Serial.print(strlen(testName));
    Serial.println(" long");
    delay(1000);
  } 
  delay(1000);
}

Screenshot of the output:

Screenshot of the output

Output as text:

Name received: dog

The sentence entered is 5 characters long
Xirb
  • 85
  • 1
  • 8

4 Answers4

15

Don't use C style arrays in modern C++. When you require a fixed size array, use std::array instead. From a memory storage point of view, they are identical.

You can then clear the array with: myarray.fill('\0')

doron
  • 27,972
  • 12
  • 65
  • 103
9

If your definition of "emptying char array" is set all elements of an array to zero, you can use std::memset.

This will allow you to write this instead of your clear loop:

const size_t arraySize = 20;  // Avoid magic numbers!
char testName[arraySize];
memset(&(testName[0]), 0, arraySize);

As for "strange" results of strlen():

strlen(str) returns "(...) the number of characters in a character array whose first element is pointed to by str up to and not including the first null character". That means, it will count characters until it finds zero.

Check content of strings you pass to strlen() - you may have white characters there (like \r or \n, for example), that were read from the input stream.

Also, an advice - consider using std::string instead of plain char arrays.


Small note: memset() is often optimized for high performance. If this in not your requirement, you can also use std::fill which is a more C++ - like way to fill array of anything:

char testName[arraySize];
std::fill(std::begin(testName), std::end(testName), '\0');

std::begin() (and std::end) works well with arrays of compile-time size.


Also, to answer @SergeyA's comment, I suggest to read this SO post and this answer.

Mateusz Grzejek
  • 11,698
  • 3
  • 32
  • 49
  • 1
    Downvoting for unsafe `memset`. Still downvoting after edit, since `std::fill` is - guess what - optimized too! There is NO REASON to use memset in C++ program. – SergeyA Feb 09 '18 at 16:19
  • 3
    @SergeyA I was already updating my answer with an `std::fill` version. But `std::memset` is often used because of its optimized implementations, so it definitely should be mentioned. I recommend using `std::string`, but since we have a code with plain `char` arrays here, wrong usage of `memset` is the least concern IMHO. – Mateusz Grzejek Feb 09 '18 at 16:28
  • 1
    Like I said above, `std::fill` is at the same performance level as `memset`, only safer. If you do not believe me, check the codegen yourself. – SergeyA Feb 09 '18 at 16:29
  • 2
    std::fill can not be optimised away; memset can. I'm pretty sure that memset is also allowed to write chunks larger than a byte at a time; while std::fill is not. – UKMonkey Feb 09 '18 at 16:29
  • 1
    @UKMonkey yes I confirm, many `memset` implementations are highly optimized, some even use spezialized instruction sets specific to the platform, and often calls to `memset` are directly inlined. – Jabberwocky Feb 09 '18 at 16:31
  • 4
    @SergeyA I would never blame somebody who proposes `memset` when the task is to set the content of the C-style `char` array. – harper Feb 09 '18 at 16:32
  • 2
    @SergeyA because allowed to and "this compiler does or doesn't" are different; especially when it doesn't – UKMonkey Feb 09 '18 at 16:33
  • 3
    @MichaelWalz and `std::fill` is either specialized to use `memset` for POD types, or optimizer generates optimized code on the fly recognizing the pattern. – SergeyA Feb 09 '18 at 16:33
  • 1
    @UKMonkey you are contradicting yourself. If you are talking about common optimizations, I just showed how `std::fill` is optimized the same way as `memset`. If you are talking about the fact that optimizations are not mandatory, than `memset` is not required to be optimized either. Stick to one. – SergeyA Feb 09 '18 at 16:35
  • @SergeyA cppreference states very explicitly that it will make end-begin assignments; and on top of that; there is no statement there that the compiler can optimise away the entire call if it's not use in the case of 'as-is' (so you could use it to wipe passwords). memset however DOES state that it can be optimised away. To me this seems like a bug in the implementation of std::fill; or an error in cppreference. And I'm not contradicting myself; as I'm talking about what the compiler is allowed to do; not what they do. – UKMonkey Feb 09 '18 at 16:39
  • @SergeyA As I said - I suggested `memset` because of the code which manipulates plain `char` arrays. I included ``std::fill` as more elegant and safer solution, designed specifically to be used in C++ (as opposed to `memset`, which comes from C). No reason to downvote this answer IMHO. – Mateusz Grzejek Feb 09 '18 at 16:40
  • 2
    @SergeyA `std::fill` is less safe than `std::memset` because you can accidentally pass iterators that point to separate ranges, and end up with undefined behaviour. `std::memset` does not have a possibility for that bug. – eerorika Feb 09 '18 at 16:40
  • 2
    @user2079303 with std::memset you can just pass random pointer and size. You can also manage to call it for non-pod types. But of course, `std::fill` with ranges would be better. – SergeyA Feb 09 '18 at 16:42
2

Yet another quick way of filling an array with zeroes, in initialization only:

char testName[20] = {};

same as

char testName[20] = {0};

Since it is a C99 feature, compiler could complain with the following

warning: ISO C forbids empty initializer braces

More here.

Anyway, looking at the OP code, there's no need at all of initializing/filling the array, it could be better like this:

#define MAX_LENGTH 20
char testName[MAX_LENGTH];
void loop()
{
  if(Serial.available())
  {
    Serial.println("Waiting for name...");
    index = 0;
    while(Serial.available())
    {
      if(index < MAX_LENGTH) 
          testName[index++] = Serial.read();
    }
    if(index < MAX_LENGTH)
    {
      testName[index] = 0;
      Serial.print("Name received: ");
      Serial.println(testName);
      Serial.print("The sentence entered is ");
      Serial.print(strlen(testName));
      Serial.println(" long");
    }
    else
      Serial.print("Name too long ...");

    delay(1000);
  } 
  delay(1000);
} 
p-a-o-l-o
  • 9,807
  • 2
  • 22
  • 35
  • 4
    Only good at the point of definition. Cannot be used elsewhere. – doron Feb 09 '18 at 16:34
  • You cannot "clear" existing arrays with zeros this way. – Mateusz Grzejek Feb 09 '18 at 16:41
  • 1
    I'd delete this answer, it's really pointless here, because the OP obviously wants to fill the array inside his loop. – Jabberwocky Feb 09 '18 at 16:42
  • @MichaelWalz Why is this answer pointless? This is useful, relevant information that people working with raw buffers need to know. – Galik Feb 09 '18 at 16:49
  • 1
    Question says: *emptying a char array in C++*, code explicitly shows that clear needs to be performed on a already existing array and the very first sentence of this answer is: *"(...) quick way of filling an array with zeroes, **in initialization only**"*... – Mateusz Grzejek Feb 09 '18 at 16:56
  • 3
    I think my answer fits the OP need, given the code posted, not the question. – p-a-o-l-o Feb 09 '18 at 17:12
  • @MateuszGrzejek The first thing the `OP`'s code does is fill an array **immediately** after definition. If the `OP` employed this answer then that loop would have been unnecessary. This is not the *whole* answer to the question but it is certainly very relevant and worth of being included among the answers. – Galik Feb 09 '18 at 17:35
  • In C, note that `char testName[20] = {};` can warn: "warning: ISO C forbids empty initializer braces [-Wpedantic]" – chux - Reinstate Monica Feb 09 '18 at 18:25
  • It's C99, yes. Thanks anyway, I will add this to the answer. – p-a-o-l-o Feb 09 '18 at 18:26
  • Btw - please keep this answers. StackOverflow has a wider audience tgan just the OP – doron Feb 09 '18 at 20:25
  • I agree with you, @doron. I'll keep it. – p-a-o-l-o Feb 09 '18 at 20:29
0

For posterity. The OP example is an Arduino sketch. Assuming here that the input is taken through the built-in Serial Monitor of the Arduino IDE. At the bottom of the Serial Monitor window is a pop-up menu where the line ending can be selected as:

  • No line ending
  • newline
  • Carriage return
  • Both NL & CR

Whatever option is selected in this menu is appended to whatever is entered in the box at the top of the Serial Monitor when the Send button is clicked or the return key is hit. So the extra two characters are most certainly the result of having the "Both NL & CR" menu option selected.

Picking the "No line ending" option will solve the problem.

FWIW, in this application, the array does not need to be "cleared". Just append a '\0' char to the last char received to make it a C-string and all will be good.