cJSON源码解析之cJSON_Print函数

文章目录

  • 前言
  • cJSON_Print是干什么的
  • cJSON_Print源码解析
    • cJSON_Print函数实现
    • print函数
      • 函数实现
      • print_value函数
      • update_offset函数
  • 总结


前言

在处理JSON数据时,我们经常需要将内存中的JSON对象转换为字符串,以便于存储或传输。在C语言的cJSON库中,这个任务由cJSON_Print函数完成。cJSON_Print函数接收一个cJSON对象作为参数,返回一个新分配的字符串,该字符串包含了JSON对象的文本表示。在这篇文章中,我们将深入探讨cJSON_Print函数的内部实现。


cJSON_Print是干什么的

这个函数的作用就是把一个cjson的结构体变成我们看得懂的字符串,仅此而已

cJSON_Print源码解析

cJSON_Print函数实现

/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
{return (char*)print(item, true, &global_hooks);
}

在cJSON_Print函数里面,他调用了print函数来实现他内部的功能,所以我们需要聚焦到print函数中

print函数

函数实现

他的函数实现非常复杂,甚至使用了goto语句

static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks)
{static const size_t default_buffer_size = 256;printbuffer buffer[1];unsigned char *printed = NULL;memset(buffer, 0, sizeof(buffer));/* create buffer */buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size);buffer->length = default_buffer_size;buffer->format = format;buffer->hooks = *hooks;if (buffer->buffer == NULL){goto fail;}/* print the value */if (!print_value(item, buffer)){goto fail;}update_offset(buffer);/* check if reallocate is available */if (hooks->reallocate != NULL){printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1);if (printed == NULL) {goto fail;}buffer->buffer = NULL;}else /* otherwise copy the JSON over to a new buffer */{printed = (unsigned char*) hooks->allocate(buffer->offset + 1);if (printed == NULL){goto fail;}memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1));printed[buffer->offset] = '\0'; /* just to be sure *//* free the buffer */hooks->deallocate(buffer->buffer);buffer->buffer = NULL;}return printed;fail:if (buffer->buffer != NULL){hooks->deallocate(buffer->buffer);buffer->buffer = NULL;}if (printed != NULL){hooks->deallocate(printed);printed = NULL;}return NULL;
}

这个print函数的主要目标是将一个cJSON对象(item)转换为其字符串表示。下面是这个函数的工作原理:

  1. 初始化:函数首先创建一个printbuffer结构体,并为其分配一块默认大小(256字节)的内存。这个printbuffer用于存储生成的字符串。

  2. 打印值:然后,函数调用print_value函数,将cJSON对象转换为字符串,并将结果存储在printbuffer中。print_value函数会根据cJSON对象的类型(如cJSON_ObjectcJSON_ArraycJSON_String等),递归地生成JSON字符串。

  3. 更新偏移量print_value函数完成后,buffer->offset将指向printbuffer中的下一个空闲位置。函数调用update_offset来更新这个偏移量。

  4. 重新分配或复制:然后,函数检查是否可以重新分配内存。如果可以(即hooks->reallocate不为NULL),那么函数就会调用hooks->reallocate来重新分配printbuffer的大小,使其刚好能够容纳生成的字符串。如果不能重新分配内存,那么函数就会分配一块新的内存,并将生成的字符串从printbuffer复制到新的内存中。

  5. 返回结果:最后,函数返回生成的字符串。如果在任何步骤中出现错误(如内存分配失败),函数就会跳转到fail标签,释放已分配的内存,并返回NULL。

print_value函数

函数实现如下:

/* Render a value to text. */
static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer)
{unsigned char *output = NULL;if ((item == NULL) || (output_buffer == NULL)){return false;}switch ((item->type) & 0xFF){case cJSON_NULL:output = ensure(output_buffer, 5);if (output == NULL){return false;}strcpy((char*)output, "null");return true;case cJSON_False:output = ensure(output_buffer, 6);if (output == NULL){return false;}strcpy((char*)output, "false");return true;case cJSON_True:output = ensure(output_buffer, 5);if (output == NULL){return false;}strcpy((char*)output, "true");return true;case cJSON_Number:return print_number(item, output_buffer);case cJSON_Raw:{size_t raw_length = 0;if (item->valuestring == NULL){return false;}raw_length = strlen(item->valuestring) + sizeof("");output = ensure(output_buffer, raw_length);if (output == NULL){return false;}memcpy(output, item->valuestring, raw_length);return true;}case cJSON_String:return print_string(item, output_buffer);case cJSON_Array:return print_array(item, output_buffer);case cJSON_Object:return print_object(item, output_buffer);default:return false;}
}

这个函数的作用就是根据不同的type类型进行字符串的输出
我们发现一个函数不是标准C函数:ensure,他是检查当前输出的字符串是否>buffer大小,如果大于就重新hook分配内存,这样就动态分配了内存了,这就非常迷惑了。
为什么这里重新分配内存了,就代表外面不需要再进行了啊,那么为什么print函数又判断分配一次内存(上个标题的4)

这是一个很好的问题。在print_value函数中,确实会根据需要动态地重新分配内存。然而,这个内存重新分配的过程是逐步进行的,也就是说,每次需要更多的空间时,就会增加一些额外的内存。这样做的好处是,我们可以确保在任何时候,printbuffer都有足够的空间来存储新的数据。

然而,当我们完成所有的打印操作后,printbuffer可能会包含一些未使用的额外空间。这是因为我们在每次重新分配内存时,通常会分配比当前需要的稍微多一些的空间,以防止频繁的内存重新分配。

这就是为什么在print函数的最后,我们还需要再次重新分配内存。这一次,我们知道了最终的字符串的确切长度,所以可以将内存大小调整为刚好足够的大小,从而释放那些未使用的额外空间。这样做可以帮助我们优化内存使用,特别是在处理大量数据时。

update_offset函数

函数实现:

/* calculate the new length of the string in a printbuffer and update the offset */
static void update_offset(printbuffer * const buffer)
{const unsigned char *buffer_pointer = NULL;if ((buffer == NULL) || (buffer->buffer == NULL)){return;}buffer_pointer = buffer->buffer + buffer->offset;buffer->offset += strlen((const char*)buffer_pointer);
}

update_offset函数在cJSON库中起着重要的作用。它的主要任务是更新printbuffer结构体中的offset字段。

在cJSON库中,printbuffer结构体用于存储生成的JSON字符串。offset字段表示当前已经使用的printbuffer的大小,也就是说,它指向printbuffer中的下一个空闲位置。

当我们向printbuffer中添加新的数据时,我们需要更新offset字段,以确保它总是指向正确的位置。这就是update_offset函数的主要任务。

通过正确地更新offset字段,我们可以确保在任何时候,printbuffer都有足够的空间来存储新的数据。这对于生成正确的JSON字符串非常重要。
当然可以。在cJSON库中,offsetprintbuffer结构体的一个字段,它表示当前已经使用的printbuffer的大小。也就是说,它指向printbuffer中的下一个空闲位置。

让我们通过一个简单的例子来理解offset的作用。假设我们有一个printbuffer,并且我们已经向其中添加了一些数据,如下所示:

printbuffer: | 'H' | 'e' | 'l' | 'l' | 'o' | ' ' | 'W' | 'o' | 'r' | 'l' | 'd' | '\0' | ... |
offset:      12

在这个例子中,printbuffer中已经存储了字符串"Hello World",并且offset的值为12,表示我们已经使用了printbuffer的前12个位置。

现在,如果我们想要向printbuffer中添加一个新的字符,比如’!',我们就可以将这个字符添加到offset所指向的位置,然后将offset加1,如下所示:

printbuffer: | 'H' | 'e' | 'l' | 'l' | 'o' | ' ' | 'W' | 'o' | 'r' | 'l' | 'd' | '!' | '\0' | ... |
offset:      13

通过这种方式,我们可以确保在任何时候,printbuffer都有足够的空间来存储新的数据。这就是offset的主要作用。

这个update_offset函数的实现原理是计算printbuffer中字符串的新长度,并更新offset。这里的offsetprintbuffer中已经使用的部分的大小,也就是下一个空闲位置的索引。

函数的步骤如下:

  • 参数检查:首先检查buffer指针和buffer->buffer是否为NULL,如果是,则直接返回,不执行任何操作。

  • 定位字符串:通过buffer->buffer + buffer->offset定位到printbuffer中当前字符串的末尾。

  • 计算长度:使用strlen函数计算从buffer_pointer指向的位置开始的字符串的长度。
    buffer->buffer + buffer->offset确实已经定位到了printbuffer中的下一个可用位置。然而,update_offset函数中的strlen调用并不是为了找到下一个可用位置,而是为了计算新添加的字符串的长度。

    print_value函数中,我们会向printbuffer中添加新的字符串。这个新的字符串可能包含多个字符,所以我们不能简单地将offset加1。相反,我们需要计算新添加的字符串的实际长度,然后将这个长度加到offset上。

    这就是为什么我们需要调用strlen(buffer_pointer)buffer_pointer指向新添加的字符串的开始位置,strlen函数会返回从这个位置开始的字符串的长度。然后,我们将这个长度加到offset上,从而正确地更新offset

  • 更新offset:将计算出的长度加到buffer->offset上,从而更新offset

这样,offset就反映了printbuffer中字符串的新长度,确保了下一次添加内容时,能够正确地追加到字符串的末尾。这个过程对于构建JSON字符串是必要的,因为它保证了字符串的连续性和正确的内存管理。希望这个解释能帮助你理解update_offset函数的作用。


总结

通过深入研究cJSON_Print函数的源码,我们可以更好地理解cJSON库是如何将内存中的JSON对象转换为字符串的。这个函数的实现虽然复杂,但却非常关键,它使得我们可以方便地将JSON对象转换为字符串,以便于存储或传输。希望这篇文章能帮助你更好地理解cJSON库的内部工作原理,以及如何在你自己的项目中使用它。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity Shader 软粒子

Unity Shader 软粒子 前言项目Shader连连看项目渲染管线设置 鸣谢 前言 当场景有点单调的时候,就需要一些粒子点缀,此时软粒子就可以发挥作用了。 使用软粒子与未使用软粒子对比图 项目 Shader连连看 这里插播一点,可以用Vertex Color与…

XML简介XML 使用教程XML的基本结构XML的使用场景

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…

汽车IVI中控开发入门及进阶(三十三):i.MX linux开发之开发板

前言: 大部分物料/芯片,不管MCU 还是SoC,都会有原厂提供配套开发板,有这样一个使用原型,在遇到问题时或者进行开发时可以使用。 i.MX 8QuadXPlus MEK board: 1、要测试display显示器,可使用i.MX mini SAS将“LVDS1_CH0”端口连接到LVDS到HDMI适配器的cable。 2、要测试…

微服务部署上线过程总结

目录 一、找到适合自己的部署方式 二、开始部署,先安装需要的环境 2.1 梳理一下都需要安装什么软件 2.2 配置数据库环境 2.3 配置redis 2.4 配置nacos 2.5 配置rabbitmq 2.6 配置docker环境 三、环境配置好了,开始部署后端 3.1 梳理后端都…

仓库管理系统12--供应商设置

1、添加供应商窗体 2、布局控件UI <UserControl x:Class"West.StoreMgr.View.SupplierView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc"http://…

使用python做飞机大战

代码地址: 点击跳转

【论文阅读】伸缩密度比估计:Telescoping Density-Ratio Estimation

文章目录 一、文章概览&#xff08;一&#xff09;问题提出&#xff08;二&#xff09;文章工作 二、判别比估计和密度鸿沟问题三、伸缩密度比估计&#xff08;一&#xff09;核心思想&#xff08;二&#xff09;路标创建&#xff08;三&#xff09;桥梁构建&#xff08;四&…

Linux 生产消费者模型

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 前言 1. 生产消费者模型 1.1 什么是生产消…

每日一题——Python实现PAT乙级1005 继续(3n+1)猜想(举一反三+思想解读+逐步优化)五千字好文

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 代码逻辑概述 时间复杂度分析 空间复杂度分析 总结 我要更强 代码优化点…

Nginx详解-安装配置等

目录 一、引言 1.1 代理问题 1.2 负载均衡问题 1.3 资源优化 1.4 Nginx处理 二、Nginx概述 三、Nginx的安装 3.1 安装Nginx 3.2 Nginx的配置文件 四、Nginx的反向代理【重点】 4.1 正向代理和反向代理介绍 4.2 基于Nginx实现反向代理 4.3 关于Nginx的location路径…

Jetson系列机载电脑创建热点模式配置方法

Jetson nano为例—— 创建热点模式配置方法 1.1、新建一个 WiFi 在屏幕右上角找到网络图标&#xff0c;点击后选择“Edit Connections”选项&#xff0c;进入选择网络连接页面&#xff0c;然后点击左下角加号&#xff0c;新建一个连接&#xff0c;类型选择 WiFi 后点击 “cre…

如何选择适合自己的巴比达内网穿透方案

选择适合自己的巴比达内网穿透方案&#xff0c;需要考虑几个关键因素&#xff0c;包括您的具体需求、安全性要求、技术水平以及预算。以下是一些选择巴比达内网穿透方案的建议步骤&#xff1a; 1. 确定需求和用途 首先&#xff0c;需要明确您希望通过内网穿透实现的具体目标和…

【linux学习---1】点亮一个LED---驱动一个GPIO

文章目录 1、原理图找对应引脚2、IO复用3、IO配置4、GPIO配置5、GPIO时钟使能6、总结 1、原理图找对应引脚 从上图 可以看出&#xff0c; 蜂鸣器 接到了 BEEP 上&#xff0c; BEEP 就是 GPIO5_IO05 2、IO复用 查找IMX6UL参考手册 和 STM32一样&#xff0c;如果某个 IO 要作为…

DP:解决路径问题

文章目录 二维DP模型如何解决路径问题有关路径问题的几个问题1.不同路径2.不同路径Ⅱ3.下降路径最小和4.珠宝的最高价值5.地下城游戏 总结 二维DP模型 二维动态规划&#xff08;DP&#xff09;模型是一种通过引入两个维度的状态和转移方程来解决复杂问题的技术。它在许多优化和…

使用VMware创建Ubuntu 24.04【一】

系列文章目录 第二章 使用Ubuntu安装Frappe-Bench【二】 文章目录 系列文章目录前言相关链接下载地址虚拟机创建与运行初始化系统中配置 前言 VMware是一个虚拟化软件&#xff0c;它允许用户在一台计算机上模拟多个虚拟计算机环境。通过使用VMware&#xff0c;用户可以轻松地…

【Python】已解决:AttributeError: ‘function’ object has no attribute ‘ELement’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;AttributeError: ‘function’ object has no attribute ‘ELement’ 一、分析问题背景 在Python编程中&#xff0c;AttributeError通常表明你试图访问一个对象…

【Linux】生物信息学常用基本命令

wget网址用于直接从网上下载某个文件到服务器&#xff0c;当然也可以直接从网上先把东西下到本地然后用filezilla这个软件来传输到服务器上。 当遇到不会的命令时候&#xff0c;可以使用man “不会的命令”来查看这个命令的详细信息。比如我想要看看ls这个命令的详细用法&…

K8S 集群节点扩容

环境说明&#xff1a; 主机名IP地址CPU/内存角色K8S版本Docker版本k8s231192.168.99.2312C4Gmaster1.23.1720.10.24k8s232192.168.99.2322C4Gwoker1.23.1720.10.24k8s233&#xff08;需上线&#xff09;192.168.99.2332C4Gwoker1.23.1720.10.24 当现有集群中的节点资源不够用&…

FFmpeg教程-三-播放pcm文件-1

目录 一&#xff0c;下载SDL 二&#xff0c;在Qt中测试 1&#xff0c;在pro文件中加入路径 2&#xff0c;在.cpp文件中加入头文件 3&#xff0c;进行测试 4&#xff0c;显示结果 一&#xff0c;下载SDL 通过编程的方式播放音视频&#xff0c;也是需要用到这2个库: FFmpeg…

2本Top,4本纯正刊,25天即录!7月刊源表已更新!

本周投稿推荐 SCI • 能源技术类&#xff0c;1.5-2.0&#xff08;来稿即录25天&#xff09; • 计算机类&#xff0c;2.0-3.0&#xff08;纯正刊29天录用&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&a…