当前位置: 首页 > 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;各位小伙伴如有喜欢的自…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...