cJSON源码解析之cJSON_Print函数

embedded/2024/10/21 6:21:37/

文章目录

  • 前言
  • 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.ppmy.cn/embedded/56087.html

相关文章

【Docker】docker 替换宿主与容器的映射端口和文件路径

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 docker 替换宿主与容器的映射端口和文件夹 1. 正文 1.1 关闭docker 服务 systemctl stop docker1.2 找到容器的配置文件 cd /var/lib/docker/contain…

基于深度学习的人脸关键点检测

1. 任务和目标 人脸关键点检测的主要任务是识别并定位人脸图像中的特定关键点,例如眼睛的角点、眉毛的顶点、鼻子的底端、嘴角等。这些关键点不仅能提供面部结构的几何信息,还可以用于分析表情、识别个体,甚至检测面部姿势。 2. 技术和方法…

Build a Large Language Model (From Scratch)第五章(gpt-4o翻译版)

来源:https://github.com/rasbt/LLMs-from-scratch?tabreadme-ov-file https://www.manning.com/books/build-a-large-language-model-from-scratch

王者荣耀对折叠机进行专门适配优化,折叠机用户喜提加强

王者荣耀针对折叠屏手机做了针对性的适配优化,把折叠机的大屏幕优势发挥了出来——适配之后,用折叠屏玩王者荣耀拥有了更大的视野、以及更大的触控响应区域。 【更贴合折叠屏的视野适配】 此前王者荣耀并没有对折叠屏进行专门的适配,表现到…

网络爬虫详解

前言 网络爬虫(Web Scraper)是一种自动化程序,用于从互联网上提取数据。它们在数据采集、数据分析、市场调研等领域有着广泛的应用。本文将详细介绍网络爬虫的原理、工具、技术和最佳实践,帮助初学者和专业人士更好地理解和使用网…

CSRF是什么攻击 该如何解决

CSRF是什么攻击 CSRF(Cross-Site Request Forgery),即跨站请求伪造攻击,也被称为“one-click attack”或“session riding”。它是一种网络攻击方式,利用已认证用户在受信任网站上的身份,诱使用户在不知情…

DB-GPT Docker部署

感谢阅读 拉取镜像linux判断拉取是否成功的方法windows判断拉取是否成功的方法 模型以及启动容器模型启动容器 界面如下(0.56): 拉取镜像 docker pull eosphorosai/dbgpt:latestlinux判断拉取是否成功的方法 docker images | grep "eo…

Webpack: 构建 NPM Library

概述 虽然 Webpack 多数情况下被用于构建 Web 应用,但与 Rollup、Snowpack 等工具类似,Webpack 同样具有完备的构建 NPM 库的能力。与一般场景相比,构建 NPM 库时需要注意: 正确导出模块内容;不要将第三方包打包进产…