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

news2024/11/25 2:42:43

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 +0200

    bump 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 empty
         qisempty(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 empty
         qisempty(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 equal
        break;
    }
    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 equal
	    return res;
	if ( c==0 ) // strings are equal
	    break;
    }
    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 equal
      break;
    if ( c1==0 ) {
      res = -1;
      break;
    }
    if (c2!=0 ) {
      res = 1;
      break;
    }
  }
  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/549012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ VTK网格模型补洞填孔

程序示例精选 C VTK网格模型补洞填孔 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<C VTK网格模型补洞填孔>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。…

如何将Windows图片查看器的背景颜色改成浅色?

现在大家基本都在使用Win10系统&#xff0c;我们在双击查看图片时&#xff0c;系统默认使用系统自带的图片&#xff08;照片&#xff09;查看器去打开图片。图片查看器的背景色默认是黑色的&#xff0c;如下所示&#xff1a;&#xff08;因为大家可能会遇到同样的问题&#xff…

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

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

最近最少使用(LRU, Least recently used)缓存算法_华为2023

思路 性能限制很高、数据量很大时&#xff0c;cin、cout肯定是不够快的。 &#xff08;1&#xff09;可以利用getchar()速度快的特性设计快读函数读取整数&#xff0c;可以做到用scanf()函数5倍的速度读入任意整数&#xff1a; #include<cstdio> // 仅正整数可用 #defi…

u盘恢复数据方法有哪些(u盘恢复数据方法)

嘿小伙伴们&#xff0c;今天咱们来聊聊一个小问题&#xff0c;就是当我们的U盘不小心丢失了重要数据&#xff0c;怎么办呢&#xff1f;没关系&#xff0c;这里我就为大家介绍几种U盘恢复数据的方法。 u盘恢复数据方法有哪些 1,首先&#xff0c;最简单粗暴的方法就是使用Windo…

Redis分片集群

目录 搭建分片集群 散列插槽 集群伸缩 故障转移 数据迁移 RedisTemplate访问分片集群 搭建分片集群 主从&#xff08;一个主节点、多个子节点&#xff0c;读写分离&#xff09;和哨兵&#xff08;解决主节点宕机问题&#xff09;可以解决高可用、高并发读的问题。但是依然…

如何更改pdf文件的默认打开程序?

在Windows系统中&#xff0c;有时安装一些软件或执行一些操作&#xff0c;会自动将打开某种类型文件的默认程序给修改掉&#xff0c;这样后续打开文件时可能会很别扭&#xff0c;于是我们想把打开文件的默认工具设置指定的软件。 以打开pdf文件为例&#xff0c;某天打开pdf文件…

基于Zynq的雷达10Gbps高速PCIE数据采集卡方案(三)软件设计

4.1 引言 本章基于第二章的分析结论&#xff0c;进行系统软件设计。软件设计包括逻辑设计、嵌入 式软件设计和上位机软件设计。在逻辑设计中&#xff0c;对 ADC 模块、 Aurora 模块、 DDR3 SDRAM 模块和 PCIE 模块进行分析和设计&#xff0c;在 Vivado 软件提供的 …

BI技巧丨计算组柱形图

PowerBI中&#xff0c;我们经常使用柱形图来进行趋势对比分析&#xff0c;通过柱形图我们可以直观展示每个月之间的差异。 但是在实际需求中&#xff0c;PowerBI原生的柱形图仅能展示一个数据标签&#xff0c;如果我们想要展示同环比的变化情况&#xff0c;往往需要将同环比的…

Metal入门学习:GPU并行计算大数组相加

一、编程指南PDF下载链接(中英文档&#xff09; 1、Metal编程指南PDF链接 https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接 https://github.com/dennie-lee/ios_te…

【王道·计算机网络】第六章 应用层

一、基本概念 1.1 应用层概述 应用层对应用程序的通信提供服务应用层协议定义&#xff1a; 应用进程交换的报文类型&#xff0c;请求还是响应?各种报文类型的语法&#xff0c;如报文中的各个字段及其详细描述字段的语义&#xff0c;即包含在字段中的信息的含义进程何时、如何…

PathWise开发(1) 将增加节点的功能移动到鼠标右键 d3.js/vue.js

PathWise(1) 从零开始搭建知识图谱/个性化学习路径/d3.js/vue.js 2023年5月20日&#xff1a;将增加节点的功能移动到鼠标右键 跑起来先 思路&#xff1a; 将我们之前的MyTableAddNode.vue&#xff0c;删除其中的内容只留下下面的表单<template><!-- <div class…

【Linux Network】高级IO

目录 前言 五种IO模型 阻塞IO 非阻塞IO 信号驱动IO IO多路转接 异步IO 小结 同步通信 vs 异步通信 阻塞 vs 非阻塞 其他高级IO 非阻塞IO fcntl函数 代码测试 高级IO&#x1f337; 前言 IO&#xff1a;所谓的I便是 input&#xff0c;所谓的O便是 output&#xff0c;简单点来说&a…

VC++6.0掌握哈希表的基本操作和掌握几种内部排序的方法

问题描述 针对某个集体中人名设计一个哈希表&#xff0c;使得平均查找长度不超过R&#xff0c;并完成相应的建表和查表程序。 1.2基本要求 假设人名为中国人姓名的汉语拼音形式。待填入哈希表的人名共有30个&#xff0c;取平均查找长度的上限为2。哈希函数用除留余数法构造&…

【掌控安全】sql注入全集

掌控安全 &#x1f525;系列专栏&#xff1a;掌控安全 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2023年5月20日&#x1f334; &#x1f36d;作者水平很有限&#xff0c;如果发现错误&…

Linux---文件操作命令(touch、cat、more)

1. touch命令 可以通过touch命令创建文件 语法&#xff1a;touch [选项] Linux路径 touch命令&#xff0c;参数必填&#xff0c;表示要创建的文件路径&#xff0c;相对、绝对、特殊路径符均可以使用。 touch 命令不光可以用来创建文件&#xff08;当指定操作文件不存在时&a…

【Redis】聊一下缓存雪崩、击穿、穿透、预热

缓存的引入带来了数据读取性能的提升&#xff0c;但是因此也引入新的问题&#xff0c;一个是数据双写一致性&#xff0c;另一个就是雪崩、击穿、穿透&#xff0c;那么如何解决这些问题&#xff0c;我们来说下对应的问题和解决方案 雪崩 缓存雪崩&#xff1a;同一时间内大量请…

pg事务:事务相关元组结构

事务相关的元组结构 元组结构中包含很多pg的mvcc所必要的信息&#xff0c;下面的内容将梳理xmin,xmax,t_ctid,cmin,cmax,combo cid,tuple id的含义和关系 物理结构 HeapTupleHeaderData相当于tuple的header&#xff0c;其结构在src/include/access/htup_details.h中定义 typ…

【BIO、NIO、AIO、Netty】

什么是IO Java中I/O是以流为基础进行数据的输入输出的&#xff0c;所有数据被串行化(所谓串行化就是数据要按顺序进行输入输出)写入输出流。简单来说就是java通过io流方式和外部设备进行交互。在Java类库中&#xff0c;IO部分的内容是很庞大的&#xff0c;因为它涉及的领域很广…

win--C盘程序员常见应用内存空间处理

写在前面&#xff1a; 本篇用于记录我对于C盘各个应用内存处理的总结&#xff0c; 文章目录 前置知识vscode的.vscode文件迁移可以移动 软件推荐wsl和docker存储管理修改安装目录压缩磁盘 pip缓存清理JetBrains系列 前置知识 在win中有着这样一个命令mklink&#xff0c;可以…