2

I meet a terrible problem. I can't compare two string!

My job is simple, every 1ms use SoftwareSerial read_card(2, 3);to check if i read a id card.And don't read a card twice.Here are my code:

bool CardReader::check()
{
    String str= read_card.readString();
    if (str.length() == 0 ) return false;
    //Log("card:%s", str);
    Serial.println("card_id");
    Serial.println(str);
    Serial.println(card);
    Serial.print("cmp:"); Serial.println(str == card);
    if (str.length() == card.length())
    {
        uchar *p1 = (uchar*)str.c_str(), *p2 = (uchar*)str.c_str();
        for (int i = 0; i < card.length(); i++)
            if (p1[i] != p2[i])
                break;
        return false;
    }
    card = str;
    return true;
}

Let's see output:

card_id
 Rq
 僐R 
cmp:1

The hex are:

00 01 52 71 02 
00 83 52 52 00 

Why the two string is equal?

Bugs are fixed.If string begin with '\0',== can't work and = can't work either.

bool CardReader::check()
{
    String str = read_card.readString();
    if (str.length() == 0) return false;
    if (str.length() == card.length())
    {
        uchar *p1 = (uchar*)str.c_str(), *p2 = (uchar*)card.c_str();
        for (int i=0; i < card.length(); i++)
        {
            Log("%d:%d", p1[i],p2[i]);
            if (p1[i] != p2[i])
                goto find_card;
        }
        return false;
    }
find_card:
    for(int i=0;i<5;i++)
        card[i]=str[i];
    return true;
}
1
  • Your question has been answered. Please, accept the best one to close this question.
    – user31481
    Commented Nov 17, 2017 at 8:20

3 Answers 3

0

Because both strings are empty. In fact, calling the method c_str() will give a an array, which has at its first position ([0]) the null terminator (0x00 or '\0'). What follows the 0x00, is just rubbish.

In fact, check the card length, it will be 0.

(btw, there are some bugs in the code too. It always return false if the string have the same length, and it will always return true otherwise. EDIT: following the Nick Gammon's answer I also noticed that you get the pointer of the same string :). I corrected also this).

I think the code should be rewritten to:

if (str.length() == card.length())
{
    uchar *p1 = (uchar*)str.c_str(), *p2 = (uchar*)card.c_str();
    for (int i = 0; i < card.length(); i++)
        if (p1[i] != p2[i])
            return false;
}
else 
    return false;
return true;

EDIT:

I add there what I wrote in the comments, for future reference, and explained in details. The problem lies on the String class, which on one hand, it allows adding '\0' characters, through the method concat(char*), but on the other, it uses the strcmp (and other C standard functions) to perform compare between strings. This leads to unpredictable results. Maybe this is a bug in the String class.

In particular, SoftwareSerial extends Stream. Stream::readString() (which is used in the program to get str), is implemented as follows:

String Stream::readString()
{
  String ret;
  int c = timedRead();
  while (c >= 0)
  {
    ret += (char)c;
    c = timedRead();
  }
  return ret;
}

The line ret += (char)c; uses operator +=, which is implemented in WString.h as:

String & operator += (char c)           {concat(c); return (*this);}

Note that readString() also allows adding any non negative value of c, including 0, i.e. '\0' - see while (c >= 0) in its code above

In turn, concat(char c) is implemented in WString.cpp as:

unsigned char String::concat(char c)
{
    char buf[2];
    buf[0] = c;
    buf[1] = 0;
    return concat(buf, 1);
}

And concat(const char *cstr, unsigned int length) is implemented as:

unsigned char String::concat(const char *cstr, unsigned int length)
{
    unsigned int newlen = len + length;
    if (!cstr) return 0;
    if (length == 0) return 1;
    if (!reserve(newlen)) return 0;
    strcpy(buffer + len, cstr);
    len = newlen;
    return 1;
}

In the above method, length (instead of strlen) is used to determine the length of the string to add, and the new length is calculated using it:

newlen = len + length;

That's the bug: you can add a '\0' char to the string, and this will also result in an increased length of the string. They should have checked if length <= strlen(cstr), before performing this concat operation.

Now, if you call the method String::equals or use the operator == (it is the same, as it is defined in WString.h as unsigned char operator == (const String &rhs) const {return equals(rhs);}), you call this code:

unsigned char String::equals(const String &s2) const
{
    return (len == s2.len && compareTo(s2) == 0);
}

Which, in turn, calls:

int String::compareTo(const String &s) const
{
    if (!buffer || !s.buffer) {
        if (s.buffer && s.len > 0) return 0 - *(unsigned char *)s.buffer;
        if (buffer && len > 0) return *(unsigned char *)buffer;
        return 0;
    }
    return strcmp(buffer, s.buffer);
}

And strcmp ends its compare operation if it first encounters a null operator (or when a compare mismatch occurs).

Therefore in your case, readString() added a null character to the "ret" String at first, and then some other characters. This created a String which begins with '\0'. Such string is equal to any other string beginning with a '\0', regardless what follows '\0'. But if you call length(), you'll get "len" (which String::concat() does not correctly calculate, and it will be non 0), as you can find in WString.h:

inline unsigned int length(void) const {return len;}

Disclaimer: future versions of WString.h and WString.cpp could have fixed this bug.

8
  • I had a think of the problem of '\0'.So how to get the real length of string?
    – erow
    Commented Aug 23, 2017 at 7:43
  • No, length is right.It returns 5 when str begin with '\0'.And it should return true when str!=card because I find a new card.So I don't think there are bugs about [email protected] addition,I found some strange things.Is it relative to memory?I have used 65%.
    – erow
    Commented Aug 23, 2017 at 8:01
  • That's strange indeed, as in fact your first check for str.length() should make your function returning false immediately. Can you update your question writing also the code of read_card.readString() ? Strange things could happen also due to misused pointers or buffer overflows...
    – next-hack
    Commented Aug 23, 2017 at 8:06
  • .read_card isSoftwareSerial.And I already print str and card.
    – erow
    Commented Aug 23, 2017 at 8:10
  • Ok, I got it. SoftwareSerial extends Stream. Stream::readString() simply adds characters, regardless their value, i.e. it concats also '\0' (i think it's a bug, they just check for >=0 instead of >0. Then String::concat(char c) also allows adding \0 chars, see WString.cpp. This lead to a malformed String, which can actually start with a \0, which is a null terminator. String class uses conventional C string functions (such as strcmp for operator == and for compareTo() method), therefore having a String with a \0 char will generate unpredicted results...
    – next-hack
    Commented Aug 23, 2017 at 8:27
1

Why the two string is equal?

        uchar *p1 = (uchar*)str.c_str(), *p2 = (uchar*)str.c_str();

Both p1 and p2 point to the same string so of course they will always compare equal.


Do you mean:

if (str == card)
   // do something
1
  • Sorry, I write the compare function because str == card didn't work.
    – erow
    Commented Aug 23, 2017 at 7:47
0

Try strcmp()

See if it works.

3
  • 1
    it wont even compile, str and card are String objects, while strcmp() works with char*.
    – next-hack
    Commented Aug 23, 2017 at 10:56
  • Not a bad idea to use c-style strings this time maybe?
    – Avamander
    Commented Aug 23, 2017 at 11:16
  • @dannyf maybe include instructions how to use char * instead of String?
    – Avamander
    Commented Aug 23, 2017 at 11:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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