cJSON使用問題記錄
cJSON庫使用筆記
JSON:: 輕量級的資料交換語言,該語言以易於讓人閱讀的文字為基礎,用來傳輸由屬性值或者序列性的值組成的資料物件。cJSON是一個輕量級的開源JSON解析器,能夠實現使用C語言生成或解析json格式的物件,本文主要記錄使用該解析器的過程中,遇到的一些問題。
New
通過了解cJSON庫中用於定義cJSON物件的結構體與cJSON_New_Item函式:
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
/* Internal constructor. */
static cJSON *cJSON_New_Item(void)
{
cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
if (node) memset(node, 0, sizeof(cJSON));
return node;
}
不難發現,本質上可以將cJSON物件理解為一個雙向連結串列。在新建cJSON物件時,需要對其進行初始化操作,不然會造成存在野指標訪問記憶體的問題。
//例項一個新的cJSON物件
cJSON *json_new = cJSON_CreateObject();
//解析一串符合json格式的字串,並新建一個cJSON指標指向其解析結果
char *str = "{\"Title\":\"Hello World!\"}";
cJSON *json_parse = cJSON_Parse(str);
Delete
既然cJSON物件的空間是被動態分配出來的,在使用完該物件後自然也需要在記憶體中釋放掉相關資源,cJSON中提供了cJSON_Delete(cJSON *obj);函式便於開發者釋放cJSON物件的空間,但是需要注意,在釋放空間時只需要釋放最上層的父節點,例如以下cJSON物件被新建後:
cJSON *json_root = cJSON_CreateObject();
cJSON *json_array = cJSON_CreateArray();
cJSON *json_item1 = cJSON_CreateObject();
cJSON *json_item2 = cJSON_CreateObject();
cJSON_AddStringToObject(json_item1, "name" , "張三");
cJSON_AddNumberToObject(json_item1, "age" , 15);
cJSON_AddStringToObject(json_item2, "name" , "李四");
cJSON_AddNumberToObject(json_item2, "age" , 20);
cJSON_AddItemToArray(json_array, json_item1);
cJSON_AddItemToArray(json_array, json_item2);
cJSON_AddNumberToObject(json_root, "index" , 0);
cJSON_AddItemToObject(json_root, "list", json_array);
char *str_json = cJSON_Print(json_root);
printf("%s", str_json);
/*
{
"index":1,
"list":[
{
"name":"張三",
"age":15
},
{
"name":"李四",
"age":20
}
]
}
*/
cJSON_Delete(json_root);
free(str_json);
可以看到,在程式的起始部分,新建了四個cJSON物件,但在後續的操作中,json_item1與json_item2先後被新增至json陣列json_list中,最後json_list被新增至json_root中,此時根據cJSON物件的結構體成員與cJSON_AddXxxToYyy函式的實現方法,能夠看出通過訪問json_root->child的子節點便能夠遍歷其他三個子節點,這時只需要對父節點呼叫cJSON_Delete()函式即可將子節點一同釋放,若在Delete父節點後又對子節點進行了Delete操作,則會引發釋放野指標的錯誤。
上述的物件需要Delete的問題其實有用過cJSON的程式猿應該都知道,接下來這個問題才是讓我真正想記錄這篇文章的原動力。。。
cJSON *json_root = cJSON_CreateObject();
cJSON_AddNumberToObject(json_root, "index" , 0);
printf(cJSON_Print(json_root));
cJSON_Delete(json_root);
上面這幾行程式碼看著一點毛病也沒有,增加、修改、輸出、釋放一氣呵成,實際執行起來也的確成功輸出了"{“index”:0}",那麼問題點在哪呢?
將cJSON_Print“剝開”,一層一層往裡面看,可以發現當需要輸出資料型別為整型時,呼叫瞭如下函式:
/* Render the number nicely from the given item into a string. */
static char *print_number(cJSON *item, printbuffer *p)
{
char *str = 0;
double d = item->valuedouble;
if (d == 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)
{
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;
}
這也就直接導致了,每列印輸出一次,記憶體中就會對應著產生一段被開闢而不被佔用的記憶體(其實看返回的char*就能夠判斷出會有這個現象了,本質上還是自己太菜+寫程式碼太不嚴謹),在電腦上可能用得好好的,但偶然有一次在STM32上用了cJSON,沒跑一會直接給我報了個HardFault。。。。急得我把程式碼拷到VS上看了波記憶體佔用情況,廢了半天功夫才找到問題。
Edit
對Json物件中的某部分內容進行修改是一項極為正常的操作,當修改的資料為整型的時候,可能大概看了一遍cJSON的結構體定義,就決定直接使用json_obj->valueint = i的方法來賦值,而但凡看一遍了"cJSON.h",就能夠發現在標頭檔案的最底部有這麼一個巨集定義:
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
由註釋可知,當Json物件中的整型資料被賦值時,需要同時設定下double值。雖然沒有遇到過,但是看網上不少老哥踩到了這個坑。而在修改string型別的值時,可以使用cJSON_ReplaceItemInObject函式對物件進行操作。
相關文章
- idea 使用問題記錄Idea
- craco使用問題記錄
- composer使用常見問題記錄
- weex使用中的問題記錄
- 問題記錄
- 記錄本周問題
- Docker問題記錄Docker
- comsol問題記錄
- [20211220]記錄使用sqlplus的小問題.txtSQL
- 【問題記錄】—.NetCore 編譯問題NetCore編譯
- 關於PaddleSharp GPU使用 常見問題記錄GPU
- 使用 ES6 Proxy 代理的 this 問題記錄
- 使用ogg 從oracle 同步mysql遇到問題記錄OracleMySql
- jQuery 遇到問題記錄jQuery
- 記錄一個問題
- .NET Core 問題記錄
- 樣式問題--記錄
- Android studio 問題記錄Android
- docker使用redis過程出現的問題記錄DockerRedis
- [20211221]記錄使用sqlplus的小問題補充.txtSQL
- 工作踩坑問題記錄
- gitlab上傳問題記錄Gitlab
- vsc練習問題記錄
- 記錄一個小問題
- 學習redis問題記錄Redis
- VirtualBox安裝問題記錄
- 記錄本周遇到的問題
- 梯度消失問題自我記錄梯度
- 字元編碼問題記錄字元
- Python 遭遇 ProxyError 問題記錄PythonError
- Spring相關問題記錄Spring
- ios 基礎問題記錄iOS
- vue2問題記錄Vue
- Laravel event 事件使用中 記錄的一個小問題Laravel事件
- 記錄 dcat-admin 使用 Laravel-activitylog 記錄使用者行為過程中遇到的問題Laravel
- scala.serilable ClassNotFoundException問題記錄Exception
- 網路問題定位工具記錄
- 記錄一次問題排查