当前位置: 首页 > news >正文

Doxygen源码分析: QCString类依赖的qstr系列C函数浅析

2023-05-20 17:02:21
ChrisZZ imzhuo@foxmailcom
Hompage https://github.com/zchrissirhcz

文章目录

    • 1. doxygen 版本
    • 2. QCString 类简介
    • 3. qstr 系列函数浅析
      • `qmemmove()`
      • `qsnprintf`
      • `qstrdup()`
      • `qstrfree()`
      • `qstrlen()`
      • `qstrcpy()`
      • `qstrncpy()`
      • `qisempty()`
      • `qstrcmp()`
      • `qstrncmp()`
      • `qisspace()`
      • `qstricmp()`
      • `qstrnicmp()`

在这里插入图片描述

1. doxygen 版本

本次使用的 doxygen 版本如下, 是 1.9.8 正式发布版本对应的 commit

$ git log
commit 5fded4215d4f9271fe92c940fc4532d4704f5be1 (HEAD -> master, upstream/master)
Author: Dimitri van Heesch <doxygen@gmail.com>
Date:   Thu May 18 22:14:30 2023 +0200bump version to 1.9.8 for development

2. QCString 类简介

QCString 是一个有意思的名字:

  • Q: 代表 Qt, QCString 是 Qt 中的一个类, 而 Qt 广泛流传使用, doxygen 中的 QCString 是基于 std::string 的重新实现, 接口不变
  • C: 代表 C 语言, CString 意思是 C 字符串, 以 \0 作为结束标识
  • String: 代表字符串

QCString 类位的实现, 位于 src/qcstring.h.

下面对 QCString 类的每个函数进行简要分析。函数较多,按数量划分为4部分。

  • part1: C函数, 例如 qisempty(), qstrlen()
  • part2: QString 类的成员函数

本文给出 part1 的解读。

3. qstr 系列函数浅析

在这里插入图片描述

这部分包括很简单的函数, 以 inline() 方式在 qcstring.h 实现; 也包括稍微复杂的函数, 在 qcstring.cpp 中实现。

按 qcstring.h 中出现的顺序,逐一简介。

qmemmove()

void *qmemmove( void *dst, const void *src, size_t len );

C 库函数 memmove 的重新实现, 功能是内存内存拷贝, 注意 src 和 dst 允许有重叠区域:

  • 如果 dst 的内存位置比 src 大, 则 dst 的最后一个元素肯定不会被 overlap, 因此现往 dst 的最后一个元素拷贝, 于是从后往前逐个字符拷贝
  • 反之, 如果 dst 的内存位置比 src 小, 则 dst 的第一个元素不会被 overlap, 因此先往 dst 的第一个元素拷贝, 于是从前往后得逐个字符拷贝
void *qmemmove( void *dst, const void *src, size_t len )
{char *d;const char *s;if ( dst > src ) {d = static_cast<char *>(dst) + len - 1;s = static_cast<const char *>(src) + len - 1;while ( len-- )*d-- = *s--;}else if ( dst < src ) {d = static_cast<char *>(dst);s = static_cast<const char *>(src);while ( len-- )*d++ = *s++;}return dst;
}

qsnprintf

此函数直接偷懒,用 snprintf 或 _snprintf

#if defined(_OS_WIN32_)
#define qsnprintf _snprintf
#else
#define qsnprintf snprintf
#endif

整个工程里没找到 _OS_WIN32_ 宏, 看来代码年久失修, 需要清理了。

qstrdup()

qstrdup() 功能是字符串拷贝,将输入的字符串的内容, 原样复制一份到新的内存中,返回这块内存。细节上要注意:

  • 如果给的输入字符串,本身是空指针, 那么返回空指针,不涉及内存申请
  • 如果输入字符串非空, 则申请的内存的大小, 等于输入字符串的 strlen 结果再加1, 加1用于填充 \0
  • 内存释放: 由调用者释放, 调用 qstfree().

代码实现如下:

//! Returns a copy of a string \a s.
//! Note that memory is passed to the caller, use qstrfree() to release.
char *qstrdup( const char *s );char *qstrdup( const char *str )
{if ( !str )return 0;char *dst = new char[qstrlen(str)+1];return strcpy( dst, str );
}

qstrfree()

qstfree() 功能是释放内存, 准确是是释放字符串内存, 因为字符串内存我们是统一用 new 申请的数组, 因此此处用 delete[] 是配对的。代码实现如下:

//! Frees the memory allocated using qstrdup().
void qstrfree( const char *s );void qstrfree( const char *str )
{delete[](str);
}

qstrlen()

//! Returns the length of string \a str, or 0 if a null pointer is passed.
inline uint32_t qstrlen( const char *str )
{ return str ? static_cast<uint32_t>(strlen(str)) : 0; }

C语言标准库有一个函数 strlen(), 它获取字符串长度, 不过它的输入不能是空指针。在 cppferencen 上给出了一个参考实现:

// https://en.cppreference.com/w/cpp/string/byte/strlen
std::size_t strlen(const char* start) {// NB: no nullptr checking!const char* end = start;for( ; *end != '\0'; ++end);return end - start;
}

可以看到, 判断字符串结束的条件是 *end != '\0', 而如果 start=NULL, 则 *end 直接等于对 0地址做 dereference 操作, 也就是 invalid memory access 了。因此 qstrlen() 特判了这种情况: 当输入空指针,也当做是字符串长度为0,返回0

qstrcpy()

C 语言标准库提供了字符串拷贝函数 strcpy(), 它拷贝输入字符串的内容到输出字符串, 包括终止符号 \0, 但是留了两个 UB(未定义行为):
(https://en.cppreference.com/w/cpp/string/byte/strcpy)

  • 存放拷贝结果的内存 dst 是用户提供的, 如果 dst 内存不够大, 行为是未定义的
  • 如果输入 src 和输出 dst 两块内存有重叠, 那也是未定义行为

在 doxygen 的 qstrcpy() 中并没有解决这两个 UB 问题; 解决了另一个问题: 当输入 src 是空指针时, 返回空指针。

inline char *qstrcpy( char *dst, const char *src )
{ return src ? strcpy(dst, src) : nullptr; }

qstrncpy()

C语言标准库提供了 strncpy 函数(https://en.cppreference.com/w/cpp/string/byte/strncpy), 功能和限制如下:

  • 拷贝最多 n 个字符, 终结字符 \0 也会拷贝
  • 如果拷贝了 n 个字符, 但是还没有拷贝到 \0, 那么拷贝结果就不包含终结符 \0, 换言之后续如果用 strlen 操作这个结果, 将导致 UB
  • 如果从 src 拷贝了所有字符(包括 \0)后, 拷贝的数量少于 n, 那么继续填充终结字符 \0
  • 如果输入内存和结果内存有重叠,行为是未定义的(UB)

相比之下, qstrnpy() 增加了两个特判:

  • 输入字符串为空指针, 则返回空指针
  • 如果 len 大于0, 则不管 strncpy 是否拷贝了 \0, qstrncpy 都会把最后一个字符置为 \0, 确保了后续使用 strlen 处理结果时不会出现 UB, 缺点是字符串内容可能少拷贝了一个字符, 并不能很好的发现。实现代码如下:
char *qstrncpy(char *dst,const char *src, size_t len);
char *qstrncpy( char *dst, const char *src, size_t len )
{if ( !src )return nullptr;strncpy( dst, src, len );if ( len > 0 )dst[len-1] = '\0';return dst;
}

qisempty()

C标准库没有类似的函数。 qisempty() 检查输入的字符串指针本身是否为空指针, 或者它的首个字符是否为0.

doxygen 原版实现:

inline bool qisempty( const char *s)
{ return s==0 || *s==0; }

个人认为更合理的实现,是使用 nullptr 和 \0, 提升一下可读性:

inline bool qisempty( const char *s)
{ return s==nullptr || *s=='\0'; }

qstrcmp()

C 标准库提供了 strcmp() 函数 (https://en.cppreference.com/w/cpp/string/byte/strcmp):

  • 根据 lhs 减去 rhs 的结果的符号, 返回 -1, 0, 1 三种取值:
    • 0: 相等
    • -1: 小于
    • 1: 大于
  • 如果 lhs 或 rhs 不是以 \0 结束的字符串, 则行为是未定义的(UB)

相比之下, qstrcmp() 提供了 str1 和 str2 是否为空指针的判断:

  • 如果都为空指针, 则判断为相等
  • 如果其中一个为空, 则认为空的较小

具体代码实现如下:

inline int qstrcmp( const char *str1, const char *str2 )
{ return (str1 && str2) ? strcmp(str1,str2) :     // both non-empty(qisempty(str1) && qisempty(str2)) ? 0 : // both emptyqisempty(str1) ? -1 : 1;                 // one empty, other non-empty
}

qstrncmp()

C语言标准库提供了 strncmp() 函数 (https://en.cppreference.com/w/cpp/string/byte/strncmp), 用于比较两个字符串, 并且比较过程中最多不超过n个字符长度

  • 返回结果仍然是用 lhs 减去 rhs, 等于0表示相等, -1表示小于, 1表示大于
  • 如果比较过程中,超出了 lhs 或 rhs 的长度, 行为是未定义的
  • 如果 lhs 或 rhs 是空指针, 行为是未定义的

相比之下, qstrncmp() 按照和 qstrcmp() 一样的策略, 特别判断了 str1 和 str2 是否为空指针:

  • 如果都为空指针, 则判断为相等
  • 如果其中一个为空, 则认为空的较小
inline int qstrncmp( const char *str1, const char *str2, size_t len )
{ return (str1 && str2) ? strncmp(str1,str2,len) :  // both non-empty(qisempty(str1) && qisempty(str2)) ? 0 :   // both emptyqisempty(str1) ? -1 : 1;                   // one empty other non-empty
}

qisspace()

C标准库没提供类似的函数。 qisspace() 判断单个字符是否为空白字符:

  • 空格
  • 制表符
  • 两种换行符: \r\n
inline bool qisspace(char c)
{ return c==' ' || c=='\t' || c=='\n' || c=='\r'; }

qstricmp()

qstrimcp() 的 i 表示 case-insensitive, 大小写不敏感的比较。它的具体实现是,如果判断为大写字母则转为小写字母,然后对应位置比较。这就引入了 toLowerChar() 函数

inline char toLowerChar(char c)
{return c>='A' && c<='Z' ? c|0x20 : c;
}

其中 A 是65开始的字符, c|0x20 表示 c + 32, 32等于97-65, a 对应到97。有点炫技的意思。

再来看 qstrimp() 的实现,感觉有点草率, 虽然判断了 *s1 到达终结符 \0, 执行 break; 但没考虑 *s2 到达 \0 的情况。

int qstricmp( const char *str1, const char *str2 );int qstricmp( const char *s1, const char *s2 )
{if ( !s1 || !s2 ){return s1 == s2 ? 0 : static_cast<int>(s2 - s1);}int res;char c;for ( ; !(res = ((c=toLowerChar(*s1)) - toLowerChar(*s2))); s1++, s2++ ){if ( !c )				// strings are equalbreak;}return res;
}

更合理的实现如下:

int qstricmp( const char *s1, const char *s2 )
{if ( !s1 || !s2 ){return s1 == s2 ? 0 : static_cast<int>(s2 - s1);}int res;char c1, c2;for ( ; ; s1++, s2++ ){c1 = toLowerChar(*s1);c2 = toLowerChar(*s2);res = c1 - c2;if ( res!=0 )break;if ( c1==0 ) {res = -1;break;}if ( c2==0 ) {res = 1;break;}}return res;
}

qstrnicmp()

qstricmp() 情况一样, doxygen 的原始实现也是有问题的, 没考虑第二个字符串的提前到达终结符 \0.

int qstrnicmp( const char *str1, const char *str2, size_t len );int qstrnicmp( const char *s1, const char *s2, size_t len )
{if ( !s1 || !s2 ){return static_cast<int>(s2 - s1);}for ( ; len--; s1++, s2++ ){char c = toLowerChar(*s1);int res = c-toLowerChar(*s2);if ( res!=0 ) // strings are not equalreturn res;if ( c==0 ) // strings are equalbreak;}return 0;
}

合理的实现如下:


int qstrnicmp( const char *s1, const char *s2, size_t len )
{if ( !s1 || !s2 ){return static_cast<int>(s2 - s1);}for ( ; len--; s1++, s2++ ){char c1 = toLowerChar(*s1);char c2 = toLowerChar(*s2);int res = c1 - c2;if ( res!=0 ) // strings are not equalbreak;if ( c1==0 ) {res = -1;break;}if (c2!=0 ) {res = 1;break;}}return 0;
}
http://www.lryc.cn/news/70129.html

相关文章:

  • 华为OD机试之一种字符串压缩表示的解压(Java源码)
  • Microsoft Project Online部署方案
  • 飞浆AI studio人工智能课程学习(3)-在具体场景下优化Prompt
  • 企业工程行业管理系统源码-专业的工程管理软件-提供一站式服务
  • Ehcache 整合Spring 使用页面、对象缓存
  • Spring Cloud中的服务路由与负载均衡
  • rails routes的使用
  • Linux基础内容(21)—— 进程消息队列和信号量
  • STM32实现基于RS485的简单的Modbus协议
  • springboot服务端接口公网远程调试 - 实现HTTP服务监听【端口映射】
  • zabbix监控之javasnmp自定义监控
  • Inertial Explorer处理pospac数据总结
  • tps和qps的区别是什么?怎么理解
  • 【Java系列】深入解析枚举类型
  • 网络原理(五):IP 协议
  • MySQL---空间索引、验证索引、索引特点、索引原理
  • 选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本
  • uvc驱动ioctl分析下
  • 数据库可视化神器,你在用哪一款呢
  • CMD与DOS脚本编程【第三章】
  • 多激光雷达手眼标定
  • SQL执行过程
  • K8S 部署 seata
  • ClickHouse:(二)数据类型
  • 项目文档(request页面代码逻辑)
  • 后端传到前端的JSON数据大写变小写--2023
  • 学习【菜鸟教程】【C++ 类 对象】【C++ 类的静态成员】
  • 计算机四大件笔记
  • 【vue上传文件——hash】
  • 【OpenCV DNN】Flask 视频监控目标检测教程 01