背景
隔壁组的同事在调试qt代码的时候遇到了一个bug,该bug发生前的代码大致如下
1 2 3 4 5 6
   |  int match = QString::compare("dv123456",userInputPwd());
  if(match == 0) {      }
 
  | 
 
该段代码一直运行正常
后面另一个同事为了增加另一个密码兼容,增加了如下代码
这里的代码写法肯定有问题的,不能使用&=符,直接两个compare结果单独判断是否为零即可
1 2 3 4 5 6 7
   |  int match = QString::compare("dv123456",userInputPwd()); match &= QString::compare("412345",userInputPwd());
  if(match == 0) {      }
 
  | 
 
大部分情况下该代码可以正常运行,但是今天发现如果用户输入dv123456的前几个字符,或者412345的前几个字符,比如d,4,则一样会进入match == 0部分代码。
我刚好在旁边看着这个bug的触发,调出了qt的源码查看QString部分的源码查看compare实现
探究
qt4 compare实现
由于我们组的芯片方案SDK中用的是qt4.8.6,所以我打开的是qt4源码中的compare实现,代码截取如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   | int QString::compare(const QString &other) const {     return ucstrcmp(constData(), length(), other.constData(), other.length()); }
  int QString::compare(const QString &other, Qt::CaseSensitivity cs) const {     if (cs == Qt::CaseSensitive)         return ucstrcmp(constData(), length(), other.constData(), other.length());     return ucstricmp(d->data, d->data + d->size, other.d->data, other.d->data + other.d->size); }
 
 
  static int ucstrcmp(const QChar *a, int alen, const QChar *b, int blen) {     if (a == b && alen == blen)         return 0;     int l = qMin(alen, blen);     int cmp = ucstrncmp(a, b, l);     return cmp ? cmp : (alen-blen); }
 
  static int ucstrncmp(const QChar *a, const QChar *b, int l) {     while (l-- && *a == *b)         a++,b++;     if (l==-1)         return 0;     return a->unicode() - b->unicode(); }
 
  | 
 
qt4中这个compare的实现还是比较简单易懂的,在调用compare(a,b)的时候,只会比较较短字符串的长度。如果遍历完成后都相同,则返回a和b的长度差,如果有不同,则返回不同字符的unicode代码差值
以compare("dv123456","dv")为例ucstrncmp中l为2,在while循环中遍历完后可以看到长度为2的子串都是相等的,那么ucstrncmp将会返回0,ucstrcmp则会返回alen-blen,最终返回应该是4。
而如果是compare("412345","dv"),在while中无法遍历完成,最终ucstrncmp将会返回4和d的ascii码差值,查表可知4的ascii码十进制是52,d为100,最后应该是-48
实际验证
为了验证该推论,在上述bug代码中增加打印如下
1 2 3 4 5 6 7 8 9 10 11
   |  int match1 = QString::compare("dv123456",userInputPwd()); int match2 = QString::compare("412345",userInputPwd()); int match = match11 & match2; qDebug() << "match1 = " << match1; qDebug() << "match2 = " << match2; qDebug() << "match  = " << match;
  if(match == 0) {      }
 
  | 
 
在输入框输入dv后,打印结果却有所出入
1 2 3
   | match1 = 1 match2 = -48 match  = 0
 
  | 
 
首先match为0则是因为1的二进制与-48的二进制刚好是0,则会误进入unlock代码段中。而match1是1则让我百思不得其解,而我手动在我的qt4环境中写了demo打印发现确实应该是4和-48,demo如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | int main(int argc, char *argv[]) {     QCoreApplication a(argc, argv);     QString s1 = "dv123456";     QString s2 = "412345";     int ret = 0;
      QString test = "dv";                             
      ret = QString::compare(s1,test);     qDebug() << "s1: ret = " << ret;
      ret = QString::compare(s2,test);     qDebug() << "s2: ret = " << ret;
      return a.exec(); }
 
  | 
 
qt5 compare实现
后面与同事沟通发现,他们那款芯片的sdk采用的是Qt5,于是我猜测是compare在qt4和qt5的实现不一致,于是拉下一个qt5的源码查看,截取如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | int QString::compare(const QString &other, Qt::CaseSensitivity cs) const Q_DECL_NOTHROW {     return qt_compare_strings(*this, other, cs); }
  static int qt_compare_strings(QStringView lhs, QStringView rhs, Qt::CaseSensitivity cs) Q_DECL_NOTHROW {     if (cs == Qt::CaseSensitive)         return ucstrcmp(lhs.begin(), lhs.size(), rhs.begin(), rhs.size());     else         return ucstricmp(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); }
  static int ucstrcmp(const QChar *a, size_t alen, const char *b, size_t blen) {     const size_t l = qMin(alen, blen);     const int cmp = ucstrncmp(a, reinterpret_cast<const uchar*>(b), l);     return cmp ? cmp : lencmp(alen, blen); }
 
  static int ucstrncmp(const QChar *a, const QChar *b, size_t l) {      }
 
  | 
 
可以看到前面几部分代码基本逻辑相同,在ucstrncmp也只是增加了许多平台优化相关,qt4和qt5最大的差别在于ucstrcmp的return语句。
1 2 3 4 5 6 7 8 9 10 11 12
   |  return cmp ? cmp : (alen-blen);
 
  return cmp ? cmp : lencmp(alen, blen);
  Q_DECL_CONSTEXPR int lencmp(Number lhs, Number rhs) Q_DECL_NOTHROW {     return lhs == rhs ? 0 :            lhs >  rhs ? 1 :              -1 ; }
 
  | 
 
可以看到qt5中不再使用alen-blen的方式,而且返回0,1,-1表示compare(a,b)的字符串长短关系,a比b长则返回1,相等则为0,a比b短为-1。
这也就是造成了qt4和qt5中同一个方法的结果差异所在。
解决
compare函数设计更多是为了用于字符串排序的工作,这种判断字符串是否完全相等的情况应该直接使用QString重写的==运算符,返回bool更加方便做判断运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   |  bool QString::operator==(const QString &other) const {     if (d->size != other.d->size)         return false;
      return qMemEquals(d->data, other.d->data, d->size); }
 
  bool operator==(const QString &s1, const QString &s2) Q_DECL_NOTHROW {     if (s1.d->size != s2.d->size)         return false;
      return qt_compare_strings(s1, s2, Qt::CaseSensitive) == 0; }
 
  |