[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析

源码地址:https://sourceforge.net/projects/cjson/

目录

0.前言

1.cJSON简介

2.项目的文件结构及CJSON结构体定义

2.1 文件结构

2.2 CJSON结构体定义及模型

3.了解项目功能(test.c的学习)

3.1 doit函数

3.2 dofile函数

3.3 create_objects函数

3.4 总结

【实现细节(cJSON.c)】

4.cJSON_Parse:字符串解析函数

4.0 cJSON_New_Item()

4.1 parse_value

4.1.0 parse_array

4.1.1 parse_string 

4.1.2 parse_number

4.1.4. parse_object

4.2 处理字符串不合法的情况

5.cJSON_Delete:释放json架构体内存

6.cJSON_Print : json架构体打印函数

6.0 应用buffer的机制 printbuffer *p

6.0.0 printbuffer 结构体

6.0.1 ensure(printbuffer *p, int needed)

6.0.2 cJSON_strdup(const char* str)

6.0.3 项目中涉及到p的场景

6.2 print_number

6.3 print_string

6.4 print_array(cJSON *item,int depth,int fmt,printbuffer *p)

6.5 print_object

7.简聊 cJSON_Hooks:自定义内存分配

7.1 cJSON_Hooks 定义

7.1 使用cJSON_Hooks

8.其他标准库函数

8.0 memset()

8.1 memcpy()

8.2 strcpy()

8.3 strchr()


0.前言

先修知识:Cpp or C语言。(博主本科学过Cpp,近期完成《Essential C++》阅读)

简介:本项目名为cJSON,其中json是一种数据交换格式,开头的c表示此项目是由c语言进行编写

项目的核心目标:基于 符合json语法规则的字符串 或者 一系列以C编写的json构造语句,将 json的各元素以结构体表示构造多个cJSON结构体之间的联系,为了实现可视化,对cJSON的打印也是其重要内容

使用方法:对照IDE上的代码,按照本博文章节顺序看代码

收获:

  • 内存的分配与释放
  • 结构体的定义与使用
  • 标准化数据包的解析方法(json)
  • 使用指针对情况进行判断并操作的算法架构(str指向输入,str2指向输出,根据str内的格式控制字符将其内容以一定格式复制到str2)
  • 初探buffer,将数据存在buffer内,并预留空间

p.s.此项目为博主接触的第一个C/Cpp项目,因此以大致弄清核心代码为目标进行学习和整理,因为没有对C进行深入学习,势必会有许多细节被忽略,如读者发现有错误或者令你拍案叫绝的精心编码设计,还请不吝赐教(>▽<)

在此感谢友人lx的帮助与心得交流,让我能在一个相对短的时间内完成对此项目的学习

1.cJSON简介

对详细的内容有兴趣请右转其他博客,若想尽快获取足以支撑本项目的基本cjson概念可阅读此节 

json是一种将多种元素组织起来的格式,其中主要考虑的元素之间的关系有两种:

  1. 并列关系,用 [ ] 表示,多个并列的元素在方括号内用 逗号 连接。用 [ ] 表示的内容称作数组。数组内的元素和与数组之间的关系为 child 
    例子:
  2. 键-值 关系,用 { } 表示,键与值的对应关系在花括号中用 冒号 表示。用 { } 表示的内容称作对象
    例子:[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析_第1张图片

 其中所谓元素,可以是 字符串(用 “ ” 括起来)、数字(包括整数、浮点数)、也可以是其他 数组 或者 对象(也就是说允许嵌套)。

2.项目的文件结构及CJSON结构体定义

2.1 文件结构

以推荐的查看/学习顺序列出

  1. cJSON.h:cJSON结构体的定义[重要]、cJSON_Hooks结构体的定义、cJSON Types定义及一堆函数签名
  2. test.c:可以看作main函数,在这里给出了项目的测试案例,调用了大量 cJSON.c 的内容,经由此做入口可窥见整个项目的内容
  3. cJSON.c:项目的核心,重点学习对象
  4. README:内容不少,相当于官方的指导手册,可惜是用英文写的,个人没有使用
  5. LICENSE:完全没啥用

2.2 CJSON结构体定义及模型

typedef struct cJSON {
   struct cJSON *next,*prev;  /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
   struct cJSON *child;      /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */

   int type;              /* The type of the item, as above. */

   char *valuestring;       /* The item's string, if type==cJSON_String */
   int valueint;           /* The item's number, if type==cJSON_Number */
   double valuedouble;          /* The item's number, if type==cJSON_Number */

   char *string;           /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;

struct cJSON定义如上,其成员列表说明:

  • cJSON 指针 next,prev:数组中多个并列的元素之间对象中多个并列的 键-值对之间 用 next 和 prev 链接
  • cJSON 指针 child:
    • 对象是一个元素,对象内部的单个 键-值对 是单个元素,对象到对象内部的第一个 键-值对,由child链接
    • 数组整体是一个元素,由数组到数组内部的首个元素,由child链接

p.s.从上述定义可以感受到,数组内部的成员,各自为一个独立的JSON;一个 键-值对 整体,为一个独立的JSON。

  • type:表示json元素内容的类型,其取值范围为宏定义
    #define cJSON_False 0
    #define cJSON_True 1
    #define cJSON_NULL 2
    #define cJSON_Number 3
    #define cJSON_String 4
    #define cJSON_Array 5
    #define cJSON_Object 6
  • valuestring:仅用于json元素的内容为字符数组的情况,用此成员记录
  • valueint:仅用于json元素的内容为数字的情况,该内容的int形式用此成员记录
  • valuedouble:仅用于json元素的内容为数字的情况,该内容的int形式用此成员记录
  • string:仅用于json元素为 键-值对 的情况,其键值(键值必为字符数组)用string表示。(也就是说其内容由valuestring / valueint+ valuedouble 表示)

模型示例:

【对象】

直接打印:

[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析_第2张图片

CJSON架构体的数据结构:

为突出结构的一致性,将没有用到的成员也列出来了,没有使用的变量成员为空,没有使用的指针成员用 / 标记

[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析_第3张图片

 【数组】

直接打印:

CJSON架构体的数据结构:

[C/Cpp项目笔记] 流程式C语言项目笔记:cJSON源码解析_第4张图片

以上两个图均用作示例,现在给出的目的在于理解CJSON元素及CJSON架构体的含义,具体构建方式可通过查看3.3 节所述的代码得到。

3.了解项目功能(test.c的学习)

这里我们首先从main函数出发,了解代码的功能,然后深入一层学习 test.c 前面定义的函数,在这些函数中会对cJSON.c的函数进行调用,用到时我们再深入学习cJSON.c

首先明确 main函数中并非是一个完整的内容,而是按顺序写了三个独立且完整的内容

解析(parse):这里的解析是指传入一个字符串(当然该字符串符合cjson的格式),然后将该字符串中的元素提取出来以cJSON的结构体进行表示,并根据字符串中的格式控制字符( [ ] 、{ }、: 、, )将cJSON结构体关联起来。

json架构体:解析一个完整的 符合json格式的字符串 所得到的内容称之为json架构体,具体实现中,json架构体由多个json struct 构成,每个json struct表示一个元素,不同的 json struct 通过 cJSON类 中的 next prev child 等关联起来。若将元素视为节点,将元素之间的关联视为连接线,那么一个完整的json可看作一个图的结构,因此我将其称之为json架构体。顺带一提,这个名词是博主自作主张的命名。

p.s. 需要注意的是,在test.c中的流程是:字符串 - 解析得到cJSON - 打印cJSON ,而打印cJSON和打印字符串本身貌似没啥区别,但是实际上在具体应用中我们的使用场景为:

  1. 解析字符串得到cJSON,即:字符串 - 解析得到cJSON
  2. 打印cJSON,即:cJSON - 打印cJSON

因此在 test.c 中看它的流程可能有种脱了裤子放p的感觉

内容1:定义了五个字符数组并通过 do_it 函数对该数组进行了解析和打印

	/* a bunch of json: */
	char text1[]="{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\":       \"rect\", \n\"width\":      1920, \n\"height\":     1080, \n\"interlace\":  false,\"frame rate\": 24\n}\n}";
	char text2[]="[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]";
	char text3[]="[\n    [32, -1, 0],\n    [1, 0, 0],\n    [0, 0, 1]\n	]\n";
	char text4[]="{\n		\"Image\": {\n			\"Width\":  800,\n			\"Height\": 600,\n			\"Title\":  \"View from 15th Floor\",\n			\"Thumbnail\": {\n				\"Url\":    \"http:/*www.example.com/image/481989943\",\n				\"Height\": 125,\n				\"Width\":  \"100\"\n			},\n			\"IDs\": [116, 943, 234, 38793]\n		}\n	}";
	char text5[]="[\n	 {\n	 \"precision\": \"zip\",\n	 \"Latitude\":  37.7668,\n	 \"Longitude\": -122.3959,\n	 \"Address\":   \"\",\n	 \"City\":      \"SAN FRANCISCO\",\n	 \"State\":     \"CA\",\n	 \"Zip\":       \"94107\",\n	 \"Country\":   \"US\"\n	 },\n	 {\n	 \"precision\": \"zip\",\n	 \"Latitude\":  37.371991,\n	 \"Longitude\": -122.026020,\n	 \"Address\":   \"\",\n	 \"City\":      \"SUNNYVALE\",\n	 \"State\":     \"CA\",\n	 \"Zip\":       \"94085\",\n	 \"Country\":   \"US\"\n	 }\n	 ]";

	/* Process each json textblock by parsing, then rebuilding: */
	doit(text1);
	doit(text2);
	doit(text3);
    doit(text4);
    doit(text5);

内容2:通过 dofile函数 对 tests文件夹下的 test1~5 文件内容,并在dofile中调用 do_it 对文件内容进行了解析和打印

	// 这里的地址需要自己注意一下,他是以test.exr而非test.c作为当前文件进行相对寻址的
    // 我这里使用的是cLION作为IDE,test.exe存放在项目目录下的cmake-build-debug文件夹下
    dofile("../tests/test1");
	dofile("../tests/test2");
	dofile("../tests/test3");
	dofile("../tests/test4");
	dofile("../tests/test5");

内容3:该函数内分别对 多个cjson元素 而非整体进行了定义,通过一系列c语句对cjson结构进行了定义

    create_objects();

3.1 doit函数

void doit(char *text)
{
	char *out;cJSON *json;
	
	json=cJSON_Parse(text);  // 通过char* 构造json
	if (!json) {printf("Error before: [%s]\n",cJSON_GetErrorPtr());}  // 若构造失败
	else
	{
		out=cJSON_Print(json);  // 将json转化成可打印的字符串
		cJSON_Delete(json);     // 释放json所占用的空间
		printf("%s\n", out);    // 打印
		free(out);              // 释放字符串所占用的空间
	}
}

如上所示此函数接受一个字符串数组 text,通过 cJSON_Parse函数 构造 json架构体,在构造成功的前提下将该架构体转换成字符串并打印,并且通过 cJSON_Delete 和 free 函数释放架构体和字符串所占用的空间。

3.2 dofile函数

/* Read a file, parse, render back, etc. */
void dofile(char *filename)  // 从文件中读取json的字符串(未解析)
{
	FILE *f;long len;char *data;
	
	f=fopen(filename,"rb");
	fseek(f,0,SEEK_END);  // 将f偏移到文件末尾
	len=ftell(f);  // 返回f的当前文件位置(即文件大小)
	fseek(f,0,SEEK_SET);  // 将f偏移到文件开头
	data=(char*)malloc(len+1);fread(data,1,len,f);fclose(f);  // 分配内存空间
	doit(data);  // 解析字符串
	free(data);  // 释放空间
}

从文件中读取字符串内容并通过doit函数解析,与内容1相比只是字符串的来源不同,无根本差别

3.3 create_objects函数

此函数比较长,但是其并非一个整体流程而是将多个完整的 json 构建过程写在了一起,主要可分为以下几个部分:

【3.3.1 变量初始化/声明部分】

一些工具性质的指针、用来生成json元素的素材变量(字符串数组、int矩阵、int数组等)

	cJSON *root,*fmt,*img,*thm,*fld;  // 声明了一堆cJSON指针
	char *out;int i;	/* declare a few. */

	/* Our "days of the week" array: */
	const char *strings[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
	/* Our matrix: */
	int numbers[3][3]={{0,-1,0},{1,0,0},{0,0,1}};
	/* Our "gallery" item: */
	int ids[4]={116,943,234,38793};
	/* Our array of "records": */
	struct record fields[2]={  // 此处可见struct的初始化,形参和实参的对应是依据顺序的
		{"zip",37.7668,-1.223959e+2,"","SAN FRANCISCO","CA","94107","US"},
		{"zip",37.371991,-1.22026e+2,"","SUNNYVALE","CA","94085","US"}};

【3.3.2 json架构体搭建部分】

这部分看起来杂乱,其实是分别构建了5个json架构体,构建的流程如下:(下文中的 Type 表示具体的元素类别,直接体现在函数名中)

  1. 构建json元素:通过 cJSON_CreateType() 实现,返回一个 Type 类型的json元素
    这里需要注意,若json元素比较简单,则通过传入参数即可完成元素内容的添加,如下代码创建了一个 json元素,其内容为 String 的 Array,值为 strings 的前七位
    root=cJSON_CreateStringArray(strings,7);
    若json元素比较复杂,则需要将其传入 AddItemTo 语句完成内容的添加,此时Create函数无传入参数,如下代码,首先创建了一个 json Object:root,之后为 root 添加内容
    	root=cJSON_CreateObject();
    	cJSON_AddItemToObject(root, "Image", img=cJSON_CreateObject());
  2. 为json元素赋值:通过 cJSON_AddItemToType 实现
    功能:在 参数1 所表示的元素中 添加 键(参数2) 与 值(参数3),如下代码首先为 root 添加一个键值对:“Image”: img,其中img是一个json object,之后为img添加了三个键值对:"Width": 800、"Height": 600、"Title": "View from 15th Floor"。
    	cJSON_AddItemToObject(root, "Image", img=cJSON_CreateObject());
    	cJSON_AddNumberToObject(img,"Width",800);
    	cJSON_AddNumberToObject(img,"Height",600);
    	cJSON_AddStringToObject(img,"Title","View from 15th Floor");
  3. 打印json架构体、释放空间

3.4 总结

出去标准库函数外,主要功能性函数仅存在于3.1、3.3中,将其整理如下:

3.1用到的主要函数有:

    json=cJSON_Parse(text);  // 使用text的内容创建json架构体,返回指向架构体首个元素的指针json
    out=cJSON_Print(json);   // 打印json指向的架构体的内容
    cJSON_Delete(json);      // 释放json指向的架构体的内存

3.3用到的主要函数有:

cJSON_CreateObject();      // 为json分配内存,设置类型,
cJSON_CreateString(A);     // 为jnson分配内存,设置类型,设置初值为A
cJSON_CreateStringArray(strings, n); // 为json分配内存,设置类型,设置初值为string的前n位
cJSON_CreateIntArray(numbers, n);    // 为json分配内存,设置类型,设置初值为numbers的前n位

cJSON_AddItemToObject(root, string, item);  // 给root添加键值对string-item
cJSON_AddNumberToObject(root, string, item);// 给root添加键值对string-item
cJSON_AddStringToObject(root, string, item);// 给root添加键值对string-item
cJSON_AddFalseToObject(root, string, item); // 给root添加键值对string-item

cJSON_Print(root);  // 打印json变量 root
cJSON_Delete(root); // 驶方json变量 root所占用的内存

【实现细节(cJSON.c)】

下面按照3.4中函数出现的顺序,整理其实现细节,对于cJSON.c中的其他函数在最后进行梳理。

4.cJSON_Parse:字符串解析函数

cJSON *cJSON_Parse(const char *value) {
    return cJSON_ParseWithOpts(value,0,0);
}
空壳子函数,仅调用了函数 cJSON_ParseWithOpts ,传入字符串名为 value ,并设置了其第二个参数和第三个参数均为0
cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
{
	const char *end=0;
	cJSON *c=cJSON_New_Item();  // 分配内存
	ep=0;
	if (!c) return 0;       /* memory fail */
    const char *tmp = skip(value);
	end=parse_value(c,skip(value));
	if (!end)	{cJSON_Delete(c);return 0;}	/* parse failure. ep is set. */

	/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
	if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
	if (return_parse_end) *return_parse_end=end;
	return c;
}

函数流程如下:

  1. 为 json架构体分配内存
  2. 解析字符串给json架构体
  3. 处理字符串不合法的情况

4.0 cJSON_New_Item()

static cJSON *cJSON_New_Item(void)
{
	cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
	if (node) memset(node,0,sizeof(cJSON));
	return node;
}

其中 cJSON_malloc 为一个函数指针,定义如下方代码,其规定了该指针指向的函数的 参数类型为size_t,返回值为void*(本身没啥含义,使用时强制转换返回值为自己想要的类型,看上一段代码,也的确使用了强制类型转换),名称为cJSON_malloc,定义如下:

static void *(*cJSON_malloc)(size_t sz) = malloc;

可以看到它定义了函数指针后使用malloc为其赋值。

memset 是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。

4.1 parse_value

static const char *parse_value(cJSON *item,const char *value)
{
	if (!value)						return 0;	/* Fail on null. */

    // 判断 value 的前n个内容,直接决定item的类型和内容
	if (!strncmp(value,"null",4))	{ item->type=cJSON_NULL;  return value+4; }  
	if (!strncmp(value,"false",5))	{ item->type=cJSON_False; return value+5; }
	if (!strncmp(value,"true",4))	{ item->type=cJSON_True; item->valueint=1;	return value+4; }

    // 通过查看函数源码可知,以下两个函数仅实现解析
	if (*value=='\"')				{ return parse_string(item,value); }
	if (*value=='-' || (*value>='0' && *value<='9'))	{ return parse_number(item,value); }

    // 以下两个函数除了解析外,内部还调用了parse_value,具备控制流程的功能
	if (*value=='[')				{ return parse_array(item,value); }
	if (*value=='{')				{ return parse_object(item,value); }

	ep=value;return 0;	/* failure. */
}

 首先注意到这是一个中转站性质的函数,输入 json架构体 item 和 期望向其中添加的字符串 value,根据 value 的内容调用不同的函数解析字符串value。

其次需要知道,无论是 parse_value 本身还是其中 if 判断后的语句块,他们的目标都是解析传入参数 value 的 第一个json元素,解析的结果并非作为返回值,而是赋给传入参数 item,返回值是删除已解析内容的value字符串。

虽然都是 if 判断,但是根据 json元素(通过value 的内容表示)不同,可大致可以分作三大类:

  • 当 json元素 单一且简单时,比如 null false true,可直接设置 item的类型 并将value删除已解析过的内容并返回
  • 当 json元素 单一单稍复杂时,比如 数字或字符串,调用 parse_string/parse_number 进行解析
    • 首字符为 \" 说明其为字符串,调用 parse_string
    • 首字符为 减号 或 大于0小于9,说明其为数字,调用 parse_number
  • 当 json元素 为数组或对象时,使用parse_array/parse_object 对其进行解析,因为数组和对象内部还包含其他json元素,因此在这两个函数比上面的都复杂并且递归调用了 parse_value 本身

4.1.0 parse_array

为什么这里要把parse_array放到第一位呢?因为本项目 test.c 的例程中,字符串都是存储在 array当中的,当调试parse_string的时候,实际上都是先经过 parse_array 做过一些处理(这里说的处理就是,对于数组最开始的 [ ,其实就是 parse_array 跳过的,当时跳过parse_array 直接看的parse_string,还以为是skip中有啥操作呢)

本函数目的为:传入表示字符串list整体的cJSON元素 (item) 和一个字符串list (value),将 value 中的每个字符串作为一个 cjson 元素并组织这些元素和item的联系。

主要工作如下:

  • 初始化 cjson 变量child,其身份为目前而言item中最新的一个json元素
  • value指向 [ 后的具体内容(使用skip跳过空格)
  • 为child分配空间
  • 使用parse_value解析value的内容到child,将解析过的字符串从value中删除(通过再parse_value中后移value的指针完成)
  • 当value以 ,(逗号) 开头时:
    • 初始化 cjson 变量 new_item
    • 为其分配空间
    • 构建其与child的关系
    • 将其设置为最新的一个json元素(通过child=new_item实现)
    • 解析value的内容到child,将解析后的字符串从value中删除

上面绿色表示两个完全一样的流程

代码如下:

static const char *parse_array(cJSON *item,const char *value)
{
	cJSON *child;
	if (*value!='[')	{ep=value;return 0;}	/* 谨慎,再判断一次value是否以[开头 */

	item->type=cJSON_Array;
	value=skip(value+1); // +1跳过 [ ,skip跳过 [ 后面的空格
	if (*value==']') return value+1;	/* empty array. */

	item->child=child=cJSON_New_Item();
	if (!item->child) return 0;		 /* memory fail */
	value=skip(parse_value(child,skip(value)));	/* skip any spacing, get the value. */
	if (!value) return 0;

	while (*value==',')
	{
		cJSON *new_item;
		if (!(new_item=cJSON_New_Item())) return 0; 	/* memory fail */
		child->next=new_item;
		new_item->prev=child;
		child=new_item;
		value=skip(parse_value(child,skip(value+1)));  // 经过这句之后,返回了数字之后的内容
		if (!value) return 0;	/* memory fail */
	}

	if (*value==']') return value+1;	/* end of array */
	ep=value;return 0;	/* malformed. */
}

4.1.1 parse_string 

这部分代码较长且首次接触时较难理解,亲身调试可以帮助你更好的完成学习

此部分代码可分为以下几个部分,具体代码在本段说明下方:

  • 初始化指针变量
    • ptr:指向输入,不断移动以遍历字符串内容
    • ptr2:指向输出,不断移动以给输出赋值
    • out:始终指向输出的首位,可视作输出字符数组本身
    • len:数组的长度
    • uc, uc2:解析 utf16 的字符串时使用的量,这里不重点考虑
  • while循环获取字符串数组的长度到len,使用cJSON_malloc分配空间到 out(本段函数有点意思)
    • 这里需要注意的是,len是本部分的主要目标,其表示字符串长度,而len的++没有放在while后的函数体中而是放在了while的判断条件中
    • 函数体中的 \\ 其实有点绕,其意义为:C语言中使用 "\ 表示 \,用 \\ 表示 \ ,因此当 s1 =  “abc” 时,首先要表示成 s2 =  \"abc\" ,若需要将s2作为内容表示,则需要表示成 s3 = \\ \" abc \\ \",也就有了下面代码中的3连\
char text1[]="[\"Jack (\\\"Bee\\\") Nimble\"]";
  • 上述情况是我们会遇到 \\\ 的唯一情况,此时我们需要获取abc的长度,因此当遇到\\的时候需要在前面加上转义字符 \,因此当ptr遍历到 \\ 时,while中首个判断条件成立,进而判断if,此时首先判断ptr == ’\\‘ 之后ptr++,之后再++,如此就将 \\\" 全部跳过,直接指向具体的内容 abc(上述代码中为Bee),继而获取字符串的长度到len。具体代码如下:
while (*ptr!='\"' && *ptr && ++len)
	if (*ptr++ == '\\')
	    ptr++;	/* Skip escaped quotes. */
out=(char*)cJSON_malloc(len+1);	/* This is how long we need for the string, roughly. */
if (!out) return 0;
  • ptr归位,使其指向字符串数组的首位,ptr2指向数组字符数组out的首位
  • while循环使ptr和ptr2逐步后移,将内容赋值给输出out(对ptr2的移动及解引用会对out的内容进行修改)
  • 0表示 ‘\0’ ,将给out的末尾赋值字符串结束符
  • 控制ptr将末尾的 \" 也跳过
  • 赋值给 valuestring

整体代码:

static const char *parse_string(cJSON *item,const char *str)
{
    // 初始化
	const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2;
	if (*str!='\"') {ep=str;return 0;}	// 谨慎之选,在判断一次str是否为\",实际上进入此函数前已通过if判断过

	//
	while (*ptr!='\"' && *ptr && ++len)
	    if (*ptr++ == '\\')
	        ptr++;	/* Skip escaped quotes. */
	out=(char*)cJSON_malloc(len+1);	/* This is how long we need for the string, roughly. */
	if (!out) return 0;

	// ptr、ptr2 归位
	ptr=str+1;ptr2=out;

	// while 循环将str赋值给out
	while (*ptr!='\"' && *ptr)
	{
		if (*ptr!='\\') *ptr2++=*ptr++;
		else
		{
			ptr++;
			switch (*ptr)
			{
				case 'b': *ptr2++='\b';	break;
				case 'f': *ptr2++='\f';	break;
				case 'n': *ptr2++='\n';	break;
				case 'r': *ptr2++='\r';	break;
				case 't': *ptr2++='\t';	break;
				case 'u':	 /* transcode utf16 to utf8. */
					uc=parse_hex4(ptr+1);ptr+=4;	/* get the unicode char. */

					if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0)	break;	/* check for invalid.	*/

					if (uc>=0xD800 && uc<=0xDBFF)	/* UTF16 surrogate pairs.	*/
					{
						if (ptr[1]!='\\' || ptr[2]!='u')	break;	/* missing second-half of surrogate.	*/
						uc2=parse_hex4(ptr+3);ptr+=6;
						if (uc2<0xDC00 || uc2>0xDFFF)		break;	/* invalid second-half of surrogate.	*/
						uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF));
					}

					len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len;
					
					switch (len) {
						case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
						case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
						case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
						case 1: *--ptr2 =(uc | firstByteMark[len]);
					}
					ptr2+=len;
					break;
				default:  *ptr2++=*ptr; break;
			}
			ptr++;
		}
	}

	// 给out添加 \0 封口
	*ptr2=0;

	// 跳过末尾的 \"
	if (*ptr=='\"') ptr++;

	// 赋值
	item->valuestring=out;
	item->type=cJSON_String;
	return ptr;
}

4.1.2 parse_number

这段代码相对比较简单,代码如下,我们纲举目张,从后往前看

最后为赋值,这里将double类型的n存作了 item->valuedouble,将强制类型转换到int的n存作了 item->valueint,之后设置了type

再往前就是对n的计算了,可以看出用到了前面的很多变量,因此也可以看出前面的量都是为了计算这个n,具体描述如下:

  1. sign:表示符号
  2. n:存储具体的数字
  3. pow表示指数,这里是为了表示小数 & 科学计数法
    1. scale:表示小数的内容
    2. subscale:科学计数法中e后面的符号
    3. signsubscale:科学计数法中e +/- 后接的数字
static const char *parse_number(cJSON *item,const char *num)
{
	double n=0,sign=1,scale=0;int subscale=0,signsubscale=1;

	if (*num=='-') sign=-1,num++;	/* Has sign? */
	if (*num=='0') num++;			/* is zero */
	if (*num>='1' && *num<='9')
	    do{
            n=(n*10.0)+(*num++ -'0');
	    } while (*num>='0' && *num<='9');	/* Number? */
	if (*num=='.' && num[1]>='0' && num[1]<='9') {num++;		do	n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');}	/* Fractional part? */
	if (*num=='e' || *num=='E')		/* Exponent? */
	{	num++;
	if (*num=='+')
	    num++;
	else if (*num=='-')
	    signsubscale=-1,num++;		/* With sign? */
		while (*num>='0' && *num<='9')
		    subscale=(subscale*10)+(*num++ - '0');	/* Number? */
	}

	n=sign*n*pow(10.0,(scale+subscale*signsubscale));	/* number = +/- number.fraction * 10^+/- exponent */
	
	item->valuedouble=n;
	item->valueint=(int)n;
	item->type=cJSON_Number;
	return num;
}

4.1.4. parse_object

这部分内容和 4.1.0 parse_array 大同小异,区别除了检测 [ 和 { 外,在每次分配内存New_Item() 和 解析内容parse_value之间多了一个parse_value的步骤,如下所示:

value=skip(parse_string(child,skip(value)));
if (!value) return 0;
child->string=child->valuestring;child->valuestring=0;
if (*value!=':') {ep=value;return 0;}	/* fail! */

确实,相比于并列关系的 数组对象 中的各个元素还用 键 来进行说明了,而这里的键一般都是string,所以多了这一步

child->string=child->valuestring;child->valuestring=0;

这步看起来有点烦,其实际为:上面代码第一行中的 parse_string 将value的首个字符串赋值给了CJSON中的 valuestring,在CJON结构体中,我们定义valuestring存储内容、string存储键 ,而实际上在 对象 中,这个位置上的内容其实不是内容而应该是 ,因此有了将 valuestring 赋给了 string,将 valuestring 置 \0 这个操作。

4.2 处理字符串不合法的情况

此部分在cJSON_ParseWithOpts的最后三行,乍一看无关紧要,但是对理解该函数流程中变量的传递还是有启发的,如下:

cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
{
	const char *end=0;
	cJSON *c=cJSON_New_Item();  // 分配内存
	ep=0;
	if (!c) return 0;       /* memory fail */
    const char *tmp = skip(value);
	end=parse_value(c,skip(value));

    // ------------------ 我们现在要讨论的内容在下面这三行!!--------------------
	if (!end)	{cJSON_Delete(c);return 0;}	/* parse failure. ep is set. */
	/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
	if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
	if (return_parse_end) *return_parse_end=end;
	return c;
}

这里的最后两个判断条件,require_null_terminated 和 return_parse_end 均默认为0,因此除非特意设置是不执行其中语句的,如果你有闲心的话可以继续跟我看看里面都写了啥。

这里首先来思考这样一个情况:对于parse_value(cJSON *item,const char *value)函数,输入的字符串是人为定义的,而解析的原则是按照json格式进行的。如果输入的字符串完全符合json格式还好,如果不符合json格式的话,可能会导致一些问题。

比如:符合json格式的字符串应该是:

char text1[] = "[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]";

现在用户输入成了
char text2[] = "[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\",\"Friday\", \"Saturday\" ]hehehehaha";

当我们对text2尽可能地解析(即对其调用parse_value)后,还是有 hehehehaha 的内容剩余

根据上面的内容可以知道其中涉及到的变量的含义,end:parse_value的返回值,在这里的返回值是把字符串value中尽可能解析后在字符串中所剩下的内容;c:解析字符串所得到的 json架构体。

下面我们依次看看这三个if的含义:

  • 当value完全被解析时(此时end为空):删除c。ok没啥问题,符合常理,对应带解析字符串完全符合json格式的情况。
  • 进入以下函数时,end非0,所以这两个if都是在value没有被完全解析时(也没有释放c所占内存时)进入的
    • 当 require_null_terminated 非0时:若end指向非空,则释放c所占内存,并将value中未完全解析的字符串赋给ep
    • 当 return_parse_end 非0时:将并将value中未完全解析的字符串赋给return_parse_end

5.cJSON_Delete:释放json架构体内存

这个不复杂但是有个细节有点迷,先贴代码

void cJSON_Delete(cJSON *c)
{
	cJSON *next;
	while (c)
	{
		next=c->next;
        // 若 & 是按位与的含义,那么下面第一个是取第九位,第二个是取第十位
		if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);  // type的第九位为0?
		if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);
		if (!(c->type&cJSON_StringIsConst) && c->string) cJSON_free(c->string);
		cJSON_free(c);
		c=next;
	}
}

输入一个 cJSON c,对其child valuestring string进行释放内存,并且对 c 中 next 所串的全部 cJSON 都进行上述操作。

这里比较有趣的的在于 while内 if判断条件中的 

!(c->type&cJSON_IsReference)

 & 表示按位与,这里将所可能用到的 type 和 cJSON_IsReference 列一下

/* cJSON Types: */
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
   
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512

可见 cJSON_IsReference = 100000000,而type最大为 6 = 0110,因此这里无论type为何,该条件均为false,取反后为true。这里博主猜想是:虽然作者没有写到项目中,但是若需要不可删除的Type时,可设置其第九位为1。

6.cJSON_Print : json架构体打印函数

cJSON_Parse 中涉及到的许多思想在此函数中也进行了广泛使用,因此这部分不再对相似的内容进行过多阐述

char *cJSON_Print(cJSON *item)				{return print_value(item,0,1,0);}

又是内部仅有一个函数调用语句的函数,设置的三个默认参数含义分别为:

  • depth、fmt:控制打印时的格式,比如加多少个空格之类的,不重点考虑
  • p:printbuffer结构体,目前项目中的p均为0,仅作为flag使用
static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
{
	char *out=0;
	if (!item) return 0;
	if (p)
	{
		switch ((item->type)&255)
		{
			case cJSON_NULL:	{out=ensure(p,5);	if (out) strcpy(out,"null");	break;}
			case cJSON_False:	{out=ensure(p,6);	if (out) strcpy(out,"false");	break;}
			case cJSON_True:	{out=ensure(p,5);	if (out) strcpy(out,"true");	break;}
			case cJSON_Number:	out=print_number(item,p);break;
			case cJSON_String:	out=print_string(item,p);break;
			case cJSON_Array:	out=print_array(item,depth,fmt,p);break;
			case cJSON_Object:	out=print_object(item,depth,fmt,p);break;
		}
	}
	else
	{
		switch ((item->type)&255)
		{
			case cJSON_NULL:	out=cJSON_strdup("null");	break;
			case cJSON_False:	out=cJSON_strdup("false");break;
			case cJSON_True:	out=cJSON_strdup("true"); break;
			case cJSON_Number:	out=print_number(item,0);break;
			case cJSON_String:	out=print_string(item,0);break;
			case cJSON_Array:	out=print_array(item,depth,fmt,0);break;
			case cJSON_Object:	out=print_object(item,depth,fmt,0);break;
		}
	}
	return out;
}

6.0 应用buffer的机制 printbuffer *p

我们注意到在上述代码中,根据p是否为空,对于 NULL/False/True 类型的 json,存在两种不同的处理方式:

// if(p)/p存在时的处理:
case cJSON_NULL:	{out=ensure(p,5);	if (out) strcpy(out,"null");	break;}

// else/p不存在时的处理:
case cJSON_NULL:	out=cJSON_strdup("null");	break;

 这里先对两种情况给一个定性地描述:

p是一个buffer,里面存放着buffer地址,buffer容量,偏移量

  • 当p不存在(使用)时,将输入字符串赋值给一个使用cJSON_malloc分配内存的char变量并返回,相对比较简单;
  • 当p存在时,将传入的字符串存在buffer p中,这时需要检查buffer容量是否足够。

6.0.0 printbuffer 结构体

typedef struct {char *buffer; int length; int offset; } printbuffer;

很简单的结构体,里面使用 char指针buffer存放内容,length 存放buffer容量,offset存放偏移量 (这个偏移量是啥含义,一直没更新过)

6.0.1 ensure(printbuffer *p, int needed)

static char* ensure(printbuffer *p,int needed)
{
	char *newbuffer;int newsize;
	if (!p || !p->buffer) return 0;  // 判断p是否存在
	needed+=p->offset;  // 所需空间 = 输入大小+偏移量
	if (needed<=p->length) return p->buffer+p->offset;  // 所需小于容量时
    
    // 所需大于容量时:
	newsize=pow2gt(needed);
	newbuffer=(char*)cJSON_malloc(newsize);  // 创建一个新buffer
	if (!newbuffer) {cJSON_free(p->buffer);p->length=0,p->buffer=0;return 0;}  // 创建失败
	if (newbuffer) memcpy(newbuffer,p->buffer,p->length);  // 创建成功
	cJSON_free(p->buffer);  // 释放老buffer并将其新的作为当前可用buffer p
	p->length=newsize;
	p->buffer=newbuffer;
	return newbuffer+p->offset;
}
  • 若输入字符串算上偏移后没超过buffer的容量,则将输入复制到buffer+偏移量的位置。
  • 若超过了buffer容量,首先创建一个newbuffer
    • 创建失败时:释放老buffer,设置其容量和地址均为0,直接返回
    • 创建成功时,将buffer中的内容复制到新buffer处,释放老buffer,返回新buffer+偏移量的位置。

6.0.2 cJSON_strdup(const char* str)

static char* cJSON_strdup(const char* str)
{
      size_t len;
      char* copy;

      len = strlen(str) + 1;  // +1表示考虑字符串末尾的 \0  
      if (!(copy = (char*)cJSON_malloc(len))) return 0;
      memcpy(copy,str,len);
      return copy;
}

 如上代码,输入字符数组 str ,返回字符数组 copy ,相比于str,copy是通过 cJSON_malloc分配的内存。

涉及到STL函数:

strlen()
memcpy()

6.0.3 项目中涉及到p的场景

涉及到p的场景,均为分配空间。有无p的区别在于,有p则将空间分配到buffer中,无p则通过 cJSON_malloc分配动态空间,比如print_number中,每次根据数据大小 x 分配空间时,都使用如下方法判断一下是用ensure还是用cJSON_malloc分配空间

if (p)	str=ensure(p,x);
else	str=(char*)cJSON_malloc(x);	/* 2^64+1 can be represented in 21 chars. */

6.2 print_number

首先记得,进入此函数的 case 为 cJSON元素 item -> type  = cJSON_Number,其传入参数为item

另外,回顾 json 结构体的定义可知 对于Number来说,内容以两种格式存放:int型的 valueint,double型的 valuedouble。

下面我们整理代码内容:

函数内部根据json元素中存储内容,判断

  1. 若valuedouble为0,则分配2位内存并通过 strcpy( ) 对返回值 str 赋值
  2. 当 valueint 和 valuedouble 相差过小(此时用valueint表征数据,可忽视valuedouble)  and valuedouble 在 INT_MAX 和 INT_MIN 之间时:
    1. 分配一定内存:int型数据最大长度为20个字符
    2. 忽视valuedouble,将 valueint 通过 strcpy( ) 对返回值 str 赋值
  3. 其他情况:
    1. 分配更大的内存(64位)
    2. 尝试用floor表示数据并对str赋值
    3. 否则 数据非常大或者非常小的时候,按照科学计数法赋值给str
    4. 否则 按照普通float的形式赋值给str
static char *print_number(cJSON *item,printbuffer *p)
{
	char *str=0;
	double d=item->valuedouble;  // 将json架构体 item 中存放的数据赋值给d
	if (d==0){
        // 若数据为0
		if (p)	str=ensure(p,2);
		else	str=(char*)cJSON_malloc(2);	/* special case for 0. */
		if (str) strcpy(str,"0");
	}else if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN){
	    // 若int型数据和double型数据差别足够小且数据大小在INT_MAX INT_MIN之间:
		if (p)	str=ensure(p,21);
		else	str=(char*)cJSON_malloc(21);	/* 2^64+1 can be represented in 21 chars. */
		if (str)	sprintf(str,"%d",item->valueint);
	}else{
	    // 其他情况
		if (p)	str=ensure(p,64);
		else	str=(char*)cJSON_malloc(64);	/* This is a nice tradeoff. */
		if (str)
		{
			if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d);
			else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9)			sprintf(str,"%e",d);
			else												sprintf(str,"%f",d);
		}
	}
	return str;
}

6.3 print_string

static char *print_string(cJSON *item,printbuffer *p)   {return print_string_ptr(item->valuestring,p);}

空壳子函数,主要是将首参从 CJSON 架构体换成了 CJSON 元素所存储的字符串[值]:valuestring,具体我们还是要看 print_string_ptr,现在知道他的传入参数是一个字符串 str,其是一个 json元素 的内容。

本段代码可分为以下几个部分,源代码在本段最下方:

  • 初始化标志变量
    • flag:当str存在 ASCⅡ码为0~32的特殊字符 or \" or \\ 时,置1
    • token:暂时存储ptr指向的内容
  • 构造输出字符串out
    • 最简单的情况:当flag为0(不存在特殊字符)时:
      • 分配空间 ( +3 分别为 “ ” \0 )
      • 将str赋值给out
      • 返回out
    • 特殊情况:当str为空时:
      • 给 out 赋值为 \"\"
      • 返回out
    • 复杂情况:对str遍历,获取有意义的字符串的长度
      • 对于特殊字符 \"\\\b\f\n\r\t,及 ASCⅡ 在32内的字符进行特殊处理
      • 为输出 out 分配内存,遍历 str 并将内容赋值给out
      • 返回out
static char *print_string_ptr(const char *str,printbuffer *p)
{
	const char *ptr;char *ptr2,*out;int len=0,flag=0;unsigned char token;

	// 判断str中是否有特殊字符,若有将flag置1
	for (ptr=str;*ptr;ptr++)
	    flag |= ((*ptr>0 && *ptr<32) || (*ptr=='\"') || (*ptr=='\\')) ? 1 : 0;
	// 若flag为0则说明无特殊字符,构造输出字符串out
	if (!flag){
		len=ptr-str;
		if (p) out=ensure(p,len+3);
		else		out=(char*)cJSON_malloc(len+3);
		if (!out) return 0;
		ptr2=out;*ptr2++='\"';
		strcpy(ptr2,str);
		ptr2[len]='\"';
		ptr2[len+1]=0;  // ASCⅡ的 0 表示 \0
		return out;
	}
	// 若str为空,构造输出字符串out
	if (!str){
		if (p)	out=ensure(p,3);
		else	out=(char*)cJSON_malloc(3);
		if (!out) return 0;
		strcpy(out,"\"\"");
		return out;
	}
	// 对于存在特殊字符的情况,构造输出字符串out
	ptr=str;
	while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++;
	                                        else if (token<32) len+=5;ptr++;}
	
	if (p)	out=ensure(p,len+3);
	else	out=(char*)cJSON_malloc(len+3);
	if (!out) return 0;

	ptr2=out;ptr=str;
	*ptr2++='\"';
	while (*ptr){
		if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++;
		else{
			*ptr2++='\\';
			switch (token=*ptr++){
				case '\\':	*ptr2++='\\';	break;
				case '\"':	*ptr2++='\"';	break;
				case '\b':	*ptr2++='b';	break;
				case '\f':	*ptr2++='f';	break;
				case '\n':	*ptr2++='n';	break;
				case '\r':	*ptr2++='r';	break;
				case '\t':	*ptr2++='t';	break;
				default: sprintf(ptr2,"u%04x",token);ptr2+=5;	break;	/* escape and print */
			}
		}
	}
	*ptr2++='\"';*ptr2++=0;
	return out;
}

6.4 print_array(cJSON *item,int depth,int fmt,printbuffer *p)

查阅此函数的调用来由可知,其默认参数为:item -> type = cJSON_Array, depth = 0, fmt = 1, printbuffer = 0。

目的是对一个 数组 类型的 CJSON进行打印。

下面整理其算法流程,源代码在本段最下方:

  • ptr指向输出out,ret暂存 数组中的元素(以字符串的格式),entries作为数组,内容为字符数组(意为 字符串数组)
  • 遍历item的child,记录其数量到 numentries,表示 数组 元素个数
  • 数组 元素为0时,返回 [] 到out
  • 根据numentries分配内存并赋0
  • 对item的每一个child 调用 print_value
    • 若print_value返回值为空则认为fail (set fail = 1) ,终止遍历
    • 结果返回至 字符串数组的数组:entries
    • 将print_value的返回值的长度加到len上
  • if fail=0,根据len分配内存
  • if fail = 1,将entries的内存释放并return 0;
  • 遍历entries
    • 根据entries内容,将其表示成字符串并赋值给out
  • 添加收尾的 ] ,返回out
static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p)
{
	char **entries;
	char *out=0,*ptr,*ret;int len=5;
	cJSON *child=item->child;
	int numentries=0,i=0,fail=0;
	size_t tmplen=0;
	
	/* How many entries in the array? */
	while (child) numentries++,child=child->next;
	/* Explicitly handle numentries==0 */
	if (!numentries){
		if (p)	out=ensure(p,3);
		else	out=(char*)cJSON_malloc(3);
		if (out) strcpy(out,"[]");
		return out;
	}

	if (p){...} // 使用buffer的情景,略
	else{
		/* Allocate an array to hold the values for each */
		entries=(char**)cJSON_malloc(numentries*sizeof(char*));
		if (!entries) return 0;
		memset(entries,0,numentries*sizeof(char*));  // (a, b, c): 复制字符 b(一个无符号字符)到参数 a 所指向的字符串的前 c 个字符
		/* Retrieve all the results: */
		child=item->child;
		while (child && !fail){
			ret=print_value(child,depth+1,fmt,0);
			entries[i++]=ret;
			if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
			child=child->next;
		}
		
		/* If we didn't fail, try to malloc the output string */
		if (!fail)	out=(char*)cJSON_malloc(len);
		/* If that fails, we fail. */
		if (!out) fail=1;

		/* Handle failure. */
		if (fail){
			for (i=0;i

6.5 print_object

查阅此函数的调用来由可知,其默认参数为:item -> type = cJSON_Object, depth = 0, fmt = 1, printbuffer = 0。

这部分相比print_array,主要差别在于:为记录对象的 键-值 对应关系,分别使用了entries、names 记录值和键

另外在打印对象(包括有数值的对象和空对象),都要以object的格式进行打印。除此以外整体流程和print_array相同不加以赘述。

7.简聊 cJSON_Hooks:自定义内存分配

7.1 cJSON_Hooks 定义

cJSON_Hooks 结构体作为 CJSON 结构体外唯一的 struct 一直很高冷,因其涉及到内存分配相关的知识,这里不细究但望眼熟尔。

typedef struct cJSON_Hooks {
      void *(*malloc_fn)(size_t sz);
      void (*free_fn)(void *ptr);
} cJSON_Hooks;
  • struct定义如上, 是我们熟悉的函数指针,首先提到这两个指针是对标malloc和free的,目的为让用户能有自定义malloc和free的空间,所以这两个指针的定义和返回值都和malloc和free相同
    • malloc_fn 是一个函数指针其返回值为 void*,从后续代码可以看出其输入应为内存的大小,对标malloc,可自定义为一个与malloc相同功能的函数
    • free_fn也是一个函数指针其返回值为void,从后续代码可以看出其输入为malloc分配后得到的一块内存,对标free,可自定义为一个与free相同功能的函数

7.1 使用cJSON_Hooks

void cJSON_InitHooks(cJSON_Hooks* hooks)
{
    if (!hooks) { /* Reset hooks */
        cJSON_malloc = malloc;
        cJSON_free = free;
        return;
    }

	cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;
	cJSON_free	 = (hooks->free_fn)?hooks->free_fn:free;
}

这里函数名虽然名为InitHooks,但是不是初始化CJSON_Hooks结构体的意义。本函数的功能为,在 CJSON_Hooks 已经完成定义的前提下,使用该量对函数 cJSON_malloc、cJSON_free 进行定义。

如上图,若传入参数 hooks 没被定义,那么久将 cJSON_malloc、cJSON_free 定义为 malloc 和 free,若 hooks 有被定义则将 cJSON_malloc、cJSON_free 定义为hooks中相应的内容

8.其他标准库函数

8.0 memset()

描述

C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

参数

  • str -- 指向要填充的内存块。
  • c -- 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
  • n -- 要被设置为该值的字符数。

功能

将已开辟内存空间 str 的首 n 个字节的值设为值 c(给空间初始化为全c);

8.1 memcpy()

描述

C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个字节到存储区 str1

参数

  • str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
  • n -- 要被复制的字节数。

功能

将字符串 str2 复制到 字符数组 str1 中,感觉意为给空间初始化为str2。(本项目中str1均使用malloc进行内存分配),

8.2 strcpy()

描述

C 库函数 char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest

需要注意的是如果目标数组 dest 不够大,而源字符串的长度又太长,可能会造成缓冲溢出的情况。

声明

下面是 strcpy() 函数的声明。

char *strcpy(char *dest, const char *src)

参数

  • dest -- 指向用于存储复制内容的目标数组。
  • src -- 要复制的字符串。

8.3 strchr()

描述

C 库函数 char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。

参数

  • str -- 要被检索的 C 字符串。
  • c -- 在 str 中要搜索的字符。

 

 

 

你可能感兴趣的:(项目经验,cpp,c语言)