当前位置: 首页 > 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;
}

相关文章:

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

2023-05-20 17:02:21 ChrisZZ imzhuofoxmailcom Hompage https://github.com/zchrissirhcz 文章目录 1. doxygen 版本2. QCString 类简介3. qstr 系列函数浅析qmemmove()qsnprintfqstrdup()qstrfree()qstrlen()qstrcpy()qstrncpy()qisempty()qstrcmp()qstrncmp()qisspace()qstr…...

华为OD机试之一种字符串压缩表示的解压(Java源码)

一种字符串压缩表示的解压 题目描述 有一种简易压缩算法&#xff1a;针对全部由小写英文字母组成的字符串&#xff0c;将其中连续超过两个相同字母的部分压缩为连续个数加该字母&#xff0c;其他部分保持原样不变。 例如&#xff1a;字符串“aaabbccccd”经过压缩成为字符串“…...

Microsoft Project Online部署方案

目录 一、前言 二、Microsoft Project Online简介 三、Microsoft Project Online的优势 1、云端部署 2、多设备支持...

飞浆AI studio人工智能课程学习(3)-在具体场景下优化Prompt

文章目录 在具体场景下优化Prompt营销场景办公效率场景日常生活场景海报背景图生成办公效率场景预设Prompt 生活场景中日常学习Prompt: 给写完的代码做文档 将优质Prompt模板化Prompt 1:Prompt 1:Prompt 2步骤文本过长而导致遗失信息的示例修改后 特殊示例 如何提升安全性主要目…...

企业工程行业管理系统源码-专业的工程管理软件-提供一站式服务

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示1…...

Ehcache 整合Spring 使用页面、对象缓存

Ehcache在很多项目中都出现过&#xff0c;用法也比较简单。一般的加些配置就可以了&#xff0c;而且Ehcache可以对页面、对象、数据进行缓存&#xff0c;同时支持集群/分布式缓存。如果整合Spring、Hibernate也非常的简单&#xff0c;Spring对Ehcache的支持也非常好。EHCache支…...

Spring Cloud中的服务路由与负载均衡

Spring Cloud中的服务路由与负载均衡 一、服务路由1. 服务发现2. 服务注册3. 服务消费4. 服务提供5. 服务路由实现 二、负载均衡1. 负载均衡的概念2. 负载均衡算法3. 负载均衡实现4. 负载均衡策略5. 使用Spring Cloud实现负载均衡 三、服务路由与负载均衡的集成1. 集成背景2. 集…...

rails routes的使用

Rails routes 是用于确定应该将请求发送到哪个控制器和操作的一种机制。在 Rails 应用程序中&#xff0c;可以通过定义路由来映射 URL 到控制器操作。可以使用 rails routes 命令查看当前应用程序中定义的所有路由。 以下是一些常见的用法&#xff1a; 查看所有路由&#xff…...

Linux基础内容(21)—— 进程消息队列和信号量

Linux基础内容&#xff08;20&#xff09;—— 共享内存_哈里沃克的博客-CSDN博客 目录 1.消息队列 1.定义 2.操作 2.信号量 1.定义 2.细节 3.延申 4.操作 3.IPC的特点共性 1.消息队列 1.定义 定义&#xff1a;是操作系统提供的内核级队列 2.操作 msgget&#xff1a;…...

STM32实现基于RS485的简单的Modbus协议

背景 我这里用STM32实现&#xff0c;其实可以搬移到其他MCU&#xff0c;之前有项目使用STM32实现Modbus协议 这个场景比较正常&#xff0c;很多时候都能碰到 这里主要是Modbus和变频器通信 最常见的是使用Modbus实现传感器数据的采集&#xff0c;我记得之前用过一些传感器都…...

springboot服务端接口公网远程调试 - 实现HTTP服务监听【端口映射】

文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...

zabbix监控之javasnmp自定义监控

1、客户端开启 java jmxremote 远程监控功能 上传 tomcat 软件包到 /opt 目录中 cd /opt tar zxvf apache-tomcat-9.0.16.tar.gz mv apache-tomcat-9.0.16 /usr/local/tomcat #配置 java jmxremote 远程监控功能 vim /usr/local/tomcat/bin/catalina.sh ...... #位置在 cygw…...

Inertial Explorer处理pospac数据总结

Inertial Explorer处理pospac数据的过程包括&#xff1a;1&#xff09;从pospac提取出gps数据和imu数据&#xff1b;2&#xff09;gps数据转成rinex格式&#xff1b;3)imu数据转成imr格式&#xff1b;4&#xff09;IE对gps数据进行PPP解算&#xff1b;5&#xff09;紧耦合融合解…...

tps和qps的区别是什么?怎么理解

区别&#xff1a;QPS指的是“每秒查询率”&#xff1b;而TPS指的是“事务数/秒”。理解&#xff1a;Tps即每秒处理事务数&#xff0c;对于一个页面的一次访问&#xff0c;形成一个Tps&#xff1b;而一次页面请求&#xff0c;可能产生多次对服务器的请求&#xff0c;服务器对这些…...

【Java系列】深入解析枚举类型

序言 即便平凡的日子仿佛毫无波澜&#xff0c;但在某个特定的时刻&#xff0c;执着的努力便会显现出它的价值和意义。 希望这篇文章能让你不仅有一定的收获&#xff0c;而且可以愉快的学习&#xff0c;如果有什么建议&#xff0c;都可以留言和我交流 问题 思考一下这寄个问题&a…...

网络原理(五):IP 协议

目录 认识IP 地址 子网掩码 作用 动态分配IP 地址 NAT 机制 认识MAC地址 MAC地址如何工作 网络设备和相关技术 集线器&#xff1a;转发所有端口 交换机&#xff1a;MAC地址转换表转发 主机&路由器&#xff1a;ARP缓存表ARP寻址 路由器&#xff1a;路由NAPT 数…...

MySQL---空间索引、验证索引、索引特点、索引原理

1. 空间索引 MySQL在5.7之后的版本支持了空间索引&#xff0c;而且支持OpenGIS几何数据模型 空间索引是对空间数据类型的字段建立的索引&#xff0c;MYSQL中的空间数据类型有4种&#xff0c;分别是&#xff1a; 类型 含义 说明 Geometry 空间数据 任何一种空间类型 Poi…...

选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本

引言 EMQX Cloud 是基于 EMQX Enterprise 构建的一款全托管云原生 MQTT 消息服务。为了满足不同客户的需求&#xff0c;EMQX Cloud 提供了三种版本供客户选择&#xff1a;Serverless 版、专有版和 BYOC 版。 本文将简要介绍这三个版本的核心区别&#xff0c;并通过三个用户故…...

uvc驱动ioctl分析下

uvc驱动ioctl分析下 文章目录 uvc驱动ioctl分析下uvc_ioctl_enum_input枚举输入uvc_query_ctrl__uvc_query_ctrluvc_ioctl_g_input 获取输入uvc_ioctl_s_input 设置输入uvc_query_v4l2_ctrluvc_ioctl_queryctrl查询控制器uvc_ioctl_query_ext_ctrl查询扩展控制器 uvc_ioctl_g_c…...

数据库可视化神器,你在用哪一款呢

唠嗑部分 在我们日常开发中&#xff0c;作为开发者&#xff0c;与数据库是肯定要打交道的&#xff0c;比如MySQL&#xff0c;Oracle、sqlserver… 那么数据库可视化工具&#xff0c;你用什么呢&#xff1f;小白今天将常用地几款工具列一下&#xff0c;各位小伙伴如有喜欢的自…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

GitHub 趋势日报 (2025年06月06日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...