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源码)
一种字符串压缩表示的解压 题目描述 有一种简易压缩算法:针对全部由小写英文字母组成的字符串,将其中连续超过两个相同字母的部分压缩为连续个数加该字母,其他部分保持原样不变。 例如:字符串“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前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目显示1…...
Ehcache 整合Spring 使用页面、对象缓存
Ehcache在很多项目中都出现过,用法也比较简单。一般的加些配置就可以了,而且Ehcache可以对页面、对象、数据进行缓存,同时支持集群/分布式缓存。如果整合Spring、Hibernate也非常的简单,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 应用程序中,可以通过定义路由来映射 URL 到控制器操作。可以使用 rails routes 命令查看当前应用程序中定义的所有路由。 以下是一些常见的用法: 查看所有路由ÿ…...
Linux基础内容(21)—— 进程消息队列和信号量
Linux基础内容(20)—— 共享内存_哈里沃克的博客-CSDN博客 目录 1.消息队列 1.定义 2.操作 2.信号量 1.定义 2.细节 3.延申 4.操作 3.IPC的特点共性 1.消息队列 1.定义 定义:是操作系统提供的内核级队列 2.操作 msgget:…...
STM32实现基于RS485的简单的Modbus协议
背景 我这里用STM32实现,其实可以搬移到其他MCU,之前有项目使用STM32实现Modbus协议 这个场景比较正常,很多时候都能碰到 这里主要是Modbus和变频器通信 最常见的是使用Modbus实现传感器数据的采集,我记得之前用过一些传感器都…...
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数据的过程包括:1)从pospac提取出gps数据和imu数据;2)gps数据转成rinex格式;3)imu数据转成imr格式;4)IE对gps数据进行PPP解算;5)紧耦合融合解…...
tps和qps的区别是什么?怎么理解
区别:QPS指的是“每秒查询率”;而TPS指的是“事务数/秒”。理解:Tps即每秒处理事务数,对于一个页面的一次访问,形成一个Tps;而一次页面请求,可能产生多次对服务器的请求,服务器对这些…...
【Java系列】深入解析枚举类型
序言 即便平凡的日子仿佛毫无波澜,但在某个特定的时刻,执着的努力便会显现出它的价值和意义。 希望这篇文章能让你不仅有一定的收获,而且可以愉快的学习,如果有什么建议,都可以留言和我交流 问题 思考一下这寄个问题&a…...
网络原理(五):IP 协议
目录 认识IP 地址 子网掩码 作用 动态分配IP 地址 NAT 机制 认识MAC地址 MAC地址如何工作 网络设备和相关技术 集线器:转发所有端口 交换机:MAC地址转换表转发 主机&路由器:ARP缓存表ARP寻址 路由器:路由NAPT 数…...
MySQL---空间索引、验证索引、索引特点、索引原理
1. 空间索引 MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型 空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是: 类型 含义 说明 Geometry 空间数据 任何一种空间类型 Poi…...
选择合适的 MQTT 云服务:一文了解 EMQX Cloud Serverless、Dedicated 与 BYOC 版本
引言 EMQX Cloud 是基于 EMQX Enterprise 构建的一款全托管云原生 MQTT 消息服务。为了满足不同客户的需求,EMQX Cloud 提供了三种版本供客户选择:Serverless 版、专有版和 BYOC 版。 本文将简要介绍这三个版本的核心区别,并通过三个用户故…...
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…...
数据库可视化神器,你在用哪一款呢
唠嗑部分 在我们日常开发中,作为开发者,与数据库是肯定要打交道的,比如MySQL,Oracle、sqlserver… 那么数据库可视化工具,你用什么呢?小白今天将常用地几款工具列一下,各位小伙伴如有喜欢的自…...
Linux开发必备:Makefile基础与实战模板解析
1. Linux开发中的Makefile基础在Linux环境下开发程序,与Windows平台最大的区别之一就是编译方式。Windows开发者通常习惯使用集成开发环境(IDE)提供的"一键编译"功能,而Linux开发者则需要掌握Makefile这一强大的构建工具。Makefile本质上是一个…...
项目管理实战:如何用关键路径算法优化你的开发周期(附Python代码示例)
项目管理实战:如何用关键路径算法优化你的开发周期(附Python代码示例) 在敏捷开发团队中,最常听到的抱怨莫过于"时间不够用"。上周我们的跨平台应用项目就遇到了典型困境:产品经理要求三周内完成支付模块重构…...
Simulink电力电子主电路设计指南:从基础模块到桥臂搭建
1. Simulink电力电子主电路设计入门 第一次接触Simulink做电力电子设计时,我被它丰富的模块库震撼到了。作为一个从硬件电路转战仿真的工程师,我发现用Simulink搭建主电路比实际焊接电路板方便太多。比如设计一个简单的AC-DC转换器,在实验室可…...
c++ 享元模式实现 c++如何运用共享技术有效支持大量细粒度对象
绝大多数情况下不需要手写享元类——字符串字面量、string_view、shared_ptr、对象池等更轻量直接;仅当对象满足“内部状态稳定外部状态频繁变化创建开销大”三条件时才值得考虑,且应优先用shared_ptr显式管理共享引用。享元模式在 C 里到底该不该手写 f…...
微服务下的跨域问题
在单体架构时代,跨域问题还不算突出;但进入微服务、前后端分离、多端统一时代,跨域几乎是每个项目必踩的坑。尤其在微服务架构下,网关、认证、分布式部署、多域名并存,让跨域变得更复杂、更隐蔽。本文从浏览器同源策略…...
Dify开发AI智能体的费用
Dify 的计费逻辑与 Coze 有显著不同,它最大的特点是“开源免费”与“云端订阅”并存。由于它不强制绑定大模型,你的总支出通常由“平台费 模型流量费”两部分组成。以下是截至 2026 年 4 月的详细费用拆解:1. 部署模式决定基础费用开源社区版…...
AI Agent Harness Engineering 重塑企业运营:从概念到落地的完整路径
AI Agent Harness Engineering 重塑企业运营:从概念到落地的完整路径 一、引言 钩子 你是否曾在企业运营中遇到过这样的场景:市场数据瞬息万变,决策团队却需要花费数周时间收集、分析信息才能做出响应;客户服务部门每天处理大量重复咨询,却仍有大量积压工单;供应链管理…...
深度解析Windows驱动管理:DriverStore Explorer高效清理冗余驱动实战指南
深度解析Windows驱动管理:DriverStore Explorer高效清理冗余驱动实战指南 【免费下载链接】DriverStoreExplorer Driver Store Explorer 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer DriverStore Explorer(简称RAPR&#x…...
SAP财务顾问必看:GGB1凭证替代实战指南,从配置到激活(OBBH)完整避坑流程
SAP财务顾问实战:GGB1凭证替代从配置到激活全流程解析 在SAP财务模块实施过程中,凭证字段的自动化处理一直是提升业务效率的关键环节。想象一下这样的场景:当财务人员录入供应商发票时,系统能自动根据预设规则填充付款条件&#x…...
Python unittest 断言(断言方法大全)
unittest 是 Python 内置的单元测试框架,断言(Assert) 是核心功能,用来判断「测试结果是否符合预期」。如果断言失败,测试用例会直接报错;断言通过,测试用例正常通过。 一、最常用的断言方法(必背) 日常写单元测试,90% 场景只用这 6 个: 表格 断言方法 作用 self.…...
