由于项目中使用到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);
使用上述代码后,程序就能够自动使用自定义的内存释放和分配函数。