由于项目中使用到json,之前使用只是参考别人的使用,未对cJSON源码进行学习,因此本文学习cJSON源码内部实现,记录学习cJSON源码的过程。
JSON相关知识:json介绍
学习的cJSON源码版本为1.7.11.
添加中文注释版本github地址为:cJSON1.7.11中文注释版

准备工作

#ifdef __WINDOWS__

/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention.  For windows you have 3 define options:

CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol

For *nix builds that support visibility attribute, you can define similar behavior by

setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS

then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does

*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall

/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif

#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type)   type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type)   __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type)   __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL

#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type)   __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif

我在MacOS上使用vscode阅读代码,此时可以通过明暗对比清楚看到哪些宏有定义:

我们可以看到此时CJSON_CDECL CJSON_STDCALL均为空。
CJSON_PUBLIC(type) 就是type,具体关于windows的不深究,以Linux下cJSON学习为重点,看到函数前面的CJSON_PUBLIC(type)自动转换为type.
关于__attribute__((visibility("default")))是控制符号可见性,参考stackoverflow

#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    void set_values (int,int);
    int area() {return width*height;}
};

void Rectangle::set_values (int x, int y) {
  width = x;
  height = y;
}

使用nm -C rectangle.o可以看到如下信息:

root@52coder:~/workspace/rectangle# nm -C rectangle.o
                 U __cxa_atexit
                 U __dso_handle
                 U _GLOBAL_OFFSET_TABLE_
000000000000006d t _GLOBAL__sub_I_rectangle.cpp
0000000000000024 t __static_initialization_and_destruction_0(int, int)
0000000000000000 T Rectangle::set_values(int, int)
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000000000000 r std::piecewise_construct
0000000000000000 b std::__ioinit

其中并没有Rectangle::area,是因为area被定义为内联函数,内联函数仅仅是替换,不需要编译。
修改代码如下,然后再查看rectangle.o的信息。

#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    void set_values (int,int);
    int area();
};

void Rectangle::set_values (int x, int y) {
  width = x;
  height = y;
}

int Rectangle::area()
{
  return width*height;
}

可以看到Rectangle::area的信息如下:

root@52coder:~/workspace/rectangle# nm -C rectangle.o
                 U __cxa_atexit
                 U __dso_handle
                 U _GLOBAL_OFFSET_TABLE_
0000000000000087 t _GLOBAL__sub_I_rectangle.cpp
000000000000003e t __static_initialization_and_destruction_0(int, int)
0000000000000000 T Rectangle::set_values(int, int)
0000000000000024 T Rectangle::area()
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
0000000000000000 r std::piecewise_construct
0000000000000000 b std::__ioinit

我们将rectangle.cpp编译成动态库:
g++ -o librectangle.so --shared rectangle.o

root@52coder:~/workspace/rectangle# nm -C librectangle.so | grep 'Rectangle::'
0000000000001116 T Rectangle::set_values(int, int)
000000000000113a T Rectangle::area()

动态符号信息如下:

root@52coder:~/workspace/rectangle# nm -CD librectangle.so | grep 'Rectangle::'
0000000000001116 T Rectangle::set_values(int, int)
000000000000113a T Rectangle::area()

使用如下方式重新编译生成librectangle.so

g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
g++ -o librectangle.so --shared rectangle.o

接着查看全局符号表和动态符号表:

root@52coder:~/workspace/rectangle# nm -C librectangle.so | grep 'Rectangle::'
0000000000001116 t Rectangle::set_values(int, int)
root@52coder:~/workspace/rectangle# nm -CD librectangle.so | grep 'Rectangle::'

我们修改代码如下:

#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    void set_values (int,int);
    __attribute__((visibility("default"))) int area();
};

void Rectangle::set_values (int x, int y) {
  width = x;
  height = y;
}

int Rectangle::area()
{
  return width*height;
}
root@52coder:~/workspace/rectangle# g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
root@52coder:~/workspace/rectangle# g++ -o librectangle.so --shared rectangle.o
root@52coder:~/workspace/rectangle# 
root@52coder:~/workspace/rectangle# nm -C librectangle.so | grep 'Rectangle::'
0000000000001116 t Rectangle::set_values(int, int)
000000000000113a T Rectangle::area()
root@52coder:~/workspace/rectangle# nm -CD librectangle.so | grep 'Rectangle::'
000000000000113a T Rectangle::area()
root@52coder:~/workspace/rectangle# 

内存管理

在自动模式下,cJSON使用默认的malloc和free函数管理内存。 在cJSON中,每个节点都是malloc而来,每个节点的string和valuestring也是malloc而来。 使用cJSON库中,使用cJSON_Delete函数可以递归释放JSON树中malloc的节点内存和字符内存。 当使用cJSON_Print函数后,需要手动释放cJSON_Print函数分配的内存,避免内存泄露。

在手动模式下,需要指定钩子cJSON_Hooks的指向。

cJSON_Hooks结构如下:

typedef struct cJSON_Hooks {
    void *(*malloc_fn)(size_t sz);
    void (*free_fn)(void *ptr);
} cJSON_Hooks;

只要通过cJSON_Hooks指定了内存分配和释放的函数,在之后的使用中将自动使用指定的函数进行内存分配和释放。 假设已经有了一个自定义的内存分配函数my_malloc, 内存释放函数my_free。使用如下:

cJSON_InitHooks *hooks = NULL;
hooks = (cJSON_InitHooks *)calloc(1, sizeof(cJSON_InitHooks));
hooks->malloc_fn = my_malloc;
hooks->free_fn = my_free;
cJSON_InitHooks(hooks);

使用上述代码后,程序就能够自动使用自定义的内存释放和分配函数。