ecnelises

My blog, collection and entry

cJSON源码分析-接口

最近因为各种忙碌,博客一直没有更新。下半学期的ACM/ICPC报名已经过去了,我没有参加,因为觉得自己对这种比赛没有什么特别的兴趣。其实话说回来,要和人「当面比」的事情,我很多都不喜欢。大概是因为从小到大的不自信导致的。这边Rails的项目快要写完了,收获不少,会找个时候专门用一篇博文记述。喜欢编程,所以就总是停不下来,想找点事情做。大家都说,提升编程能力要做项目。这话不假。但是编程就像写作文,初中生洋洋洒洒写个小说,很可能只会被成年人看作是幼稚文章。编程作为一门手艺,阅读他人的源码也是很重要的。感谢自由软件运动让我们有大量的源码可以用来学习,也感谢GitHub这样的平台能让我们更加方便地获取和发布源代码。所以最近可能会陆陆续续地更新一些程序的分析,也借这个过程提高一下自己细粒度层面的编程水平。希望不会烂尾。(flag已立!)

要分析呢,就从最熟悉的语言开始吧,也就是C咯。然后找一个简单的开源项目。好啦,中央已经决定了,就是cJSON了。同济2014级软件学院的C语言期末作业就是要求写一个cJSON解析器,后来我才发现那个提供的头文件就是cJSON里的……

<!--more-->

软件的第一要义就是,它创造出来必须有用。那cJSON,顾名思义,就是一个程序,它能够:

  1. 将文本化的JSON转化为C语言可以操作的数据结构(解析)
  2. 将内存里的数据结构输出为格式化的JSON字符串(序列化)

具体JSON是什么格式,不用我再多说了吧。在JavaScript流行的今天,JSON由于其简单的语法和支持嵌套的特点,得到了广泛应用。许多框架的配置文件什么的,都是用的JSON格式。也有许多人试图用JSON逐渐取代XML语言。

我们先来看源码的文件结构:

  • cJSON.c
  • cJSON.h
  • test.c
  • README.md
  • LICENSE
  • CMakeLists.txt
  • Makefile
  • cJSON_Utils.h
  • cJSON_Utils.c
  • test_utils.c
  • test/

其中末尾带util的都是针对JSON做的一些扩展(RFC6901和RFC6902),我们先略去不谈。其实作为库,核心部分就两个文件,一个cJSON.h声明,一个cJSON.c实现。那么我们就先来看看头文件,cJSON到底提供了哪些接口。

```c
/* cJSON Types: */

define cJSON_False (1 << 0)

define cJSON_True (1 << 1)

define cJSON_NULL (1 << 2)

define cJSON_Number (1 << 3)

define cJSON_String (1 << 4)

define cJSON_Array (1 << 5)

define cJSON_Object (1 << 6)

define cJSON_IsReference 256

define cJSON_StringIsConst 512

/* The cJSON structure: /
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==cJSONString /
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;

typedef struct cJSONHooks {
void (malloc
fn)(sizet sz);
void (*free
fn)(void *ptr);
} cJSON_Hooks;
```

整个分成三个部分,一个是标记Type的宏,包括了cJSON结构体里type成员的所有取值。它在这里额外增加了IsReference和StringIsConst两个类型标记。我们注意到作者表示cJSON的类型不是用的正常的自然数的顺序排布,而是利用位移运算构成了等比数列。为什么要这样呢?因为这样的话一个类型就可以和IsReference和StringIsConst叠加了。这是C语言里的常用技巧。

再往下,我们可以看到作者定义了一个叫做cJSON_Hooks的结构,包含了malloc_fn和free_fn两个函数指针作为成员。很容易看出来这两个函数指针的原型也刚好对应malloc和free的函数原型。虽然还没有开始阅读源代码,不过我们自信地猜想,这个结构的作用类似于C++ STL中的allocator,负责标准分配之外的分配方式。话说我一开始写maolang的容器的时候也用过这个方法,但是后来觉得太累赘而放弃了。

再看下面的代码。

```c
/* Supply malloc, realloc and free functions to cJSON /
extern void cJSONInitHooks(cJSONHooks
hooks);

/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSONDelete when finished. */
extern cJSON *cJSON
Parse(const char value);
/
Render a cJSON entity to text for transfer/storage. Free the char* when finished. /
extern char *cJSON_Print(cJSON *item);
/
... */

/* Returns the number of items in an array (or object). /
extern int cJSON_GetArraySize(cJSON *array);
/
... /
/
For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSONParse() returns 0. 0 when cJSONParse() succeeds. */
extern const char *cJSON_GetErrorPtr(void);
```

限于篇幅,把更多的函数声明省略了。函数声明前面加上extern关键字是可选的,仅仅是标注它是一个外部链接的函数而已。然后是一堆用以创建、删除、插入、修改JSON结构的函数,更详细的内容在头文件里。

c
/* Duplicate a cJSON item */
extern cJSON *cJSON_Duplicate(cJSON *item,int recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
need to be released. With recurse!=0, it will duplicate any children connected to the item.
The item->next and ->prev pointers are always zero on return from Duplicate. */

复制的函数有一个额外参数,表示是否选择递归复制(深拷贝)。这里还有一些函数,我们分析实现的时候再说。在头文件的最后还有一个有趣的宏:

```c
/* Macro for iterating over an array */

define cJSON_ArrayForEach(pos, head) \

for(pos = (head)->child; pos != NULL; pos = pos->next)
```

虽然仅仅是简陋的宏替换,不过还真是搞出了现代语言的感觉呢。

看完头文件之后,我们发现,cJSON这个简单的解析器,名堂却不小,提供了不少实用的接口。至于这些接口内部实现的细节,我们下一篇文章再来讨论啦。