javascript
cJSON库源码分析
cJSON是一個(gè)超輕巧,攜帶方便,單文件,簡(jiǎn)單的可以作為ANSI-C標(biāo)準(zhǔn)的Json格式解析庫(kù)。
? ? ? ???那什么是Json格式?這里照搬度娘百科的說(shuō)法:
? ? ? ???Json(JavaScript Object Notation) 是一種輕量級(jí)的數(shù)據(jù)交換格式。它基于JavaScript(Standard ECMA-262 3rd Edition – December 1999)的一個(gè)子集。JSON采用完全獨(dú)立于語(yǔ)言的文本格式,但是也使用了類似于C語(yǔ)言家族的習(xí)慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成為理想的數(shù)據(jù)交換語(yǔ)言。易于人閱讀和編寫,同時(shí)也易于機(jī)器解析和生成。
? ? ? ???更加詳細(xì)的解釋和示例請(qǐng)查看?http://www.json.org/?主頁(yè)。
? ? ? ???其實(shí)簡(jiǎn)單說(shuō),Json就是一種信息交換格式,而cJSON其實(shí)就是對(duì)Json格式的字符串進(jìn)行構(gòu)建和解析的一個(gè)C語(yǔ)言函數(shù)庫(kù)。
? ? ? ???可以在以下地址下載到cJSON的源代碼:
? ? ? ???http://sourceforge.net/projects/cjson/
? ? ? ???__MACOSX目錄是提供給Mac OS的源碼,我的機(jī)器運(yùn)行的是Fedora 18,所以選擇另外一個(gè)目錄即可。
? ? ? ???簡(jiǎn)單的閱讀下README文件,先學(xué)習(xí)cJSON庫(kù)的使用方法。若是連庫(kù)都還不會(huì)使用,分析源碼就無(wú)從談起了。通過(guò)簡(jiǎn)單的了解,我們得知cJSON庫(kù)實(shí)際上只有cJSON.c和cJSON.h兩個(gè)文件組成,絕對(duì)輕量級(jí)。
? ? ? ???不過(guò),代碼風(fēng)格貌似有點(diǎn)非主流,先用indent格式化一下代碼吧。我個(gè)人喜歡K&R風(fēng)格的代碼,使用的indent命令行參數(shù)如下:
| 1 | indent?-?bad?-?bli?0?-?ce?-?kr?-?nsob?--?space?-?after?-?if?--?space?-?after?-?while?--?space?-after?-?for?--?use?-?tabs?-?i8 |
? ? ? ???格式化之后,代碼結(jié)構(gòu)看起來(lái)清晰多了。
? ? ? ???那么,從何處下手來(lái)分析呢?打開(kāi)代碼文件逐行閱讀么?當(dāng)然不是了,有main函數(shù)的程序大都是從main函數(shù)開(kāi)始分析,那么沒(méi)有main函數(shù)的純函數(shù)庫(kù)呢?那就自己寫main函數(shù)唄。
? ? ? ???cJSON作為Json格式的解析庫(kù),其主要功能無(wú)非就是構(gòu)建和解析Json格式了,我們先寫一個(gè)構(gòu)建Json格式字符串的程序,盡可能的使其用到的類型多一點(diǎn)(事實(shí)上README文件里提供了不錯(cuò)的示例代碼,我們直接借鑒一下吧)。代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h> #include <stdlib.h> #include "cJSON.h" int?main?(?int?argc?,?char?*?argv?[?]?) {?????cJSON?*?root?,?*?fmt?; ?????root?=?cJSON_CreateObject?(?)?; ?????cJSON_AddStringToObject?(?root?,?"name"?,?"Jack (\"Bee\") Nimble"?)?; ?????fmt?=?cJSON_CreateObject?(?)?; ?????cJSON_AddItemToObject?(?root?,?"format"?,?fmt?)?; ?????cJSON_AddStringToObject?(?fmt?,?"type"?,?"rect"?)?; ?????cJSON_AddNumberToObject?(?fmt?,?"width"?,?1920?)?; ?????cJSON_AddFalseToObject?(?fmt?,?"interlace"?)?; ?????char?*?result?=?cJSON_Print?(?root?)?; ?????puts?(?result?)?; ?????free?(?result?)?; ?????cJSON_Delete?(?root?)?; ?????return?EXIT_SUCCESS?; } |
? ? ? ??編譯運(yùn)行后?(?編譯時(shí)注意要鏈接數(shù)學(xué)庫(kù),參數(shù)行要加?-lm)?,運(yùn)行結(jié)果如下:
| 12345678 | { ?????"name"?:?"Jack (\"Bee\") Nimble"?, "format"?:?{?????????"type"?:?"rect"?, ?????????"width"?:?1920?, ?????????"interlace"?:?false }} |
? ? ? ? 打開(kāi)cJSON.h這個(gè)頭文件,我們可以看到每一個(gè)節(jié)點(diǎn),實(shí)際上都是由cJSON這個(gè)結(jié)構(gòu)體來(lái)描述的:
| 1 2 3 4 5 6 7 8 9 10 11 12 | typedef?struct?cJSON?{ ?????struct?cJSON?*?next?,?*?prev?; ?????struct?cJSON?*?child?; int?type?;?????char?*?valuestring?; ?????int?valueint?; ?????double?valuedouble?; ?????char?*?string?; }?cJSON?; |
? ? ? ??結(jié)合這個(gè)結(jié)構(gòu)體和上面相關(guān)API的調(diào)用,其實(shí)我們大概可以猜測(cè)出cJSON對(duì)于Json格式的描述和處理的方法了:
? ? ? ???每一個(gè)cJSON結(jié)構(gòu)都描述了一項(xiàng)”鍵-值”對(duì)的數(shù)據(jù),其中next和prev指針顯然是指向同級(jí)前后的cJSON結(jié)構(gòu),而child指針自然是指向孩子節(jié)點(diǎn)的cJSON結(jié)構(gòu)。type類型顯然是為了區(qū)分值的類型而設(shè)置的,在cJSON.h文件一開(kāi)始就定義了這些類型的值:
| 12345678 | /* 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 |
? ? ? ??很顯然通過(guò)檢測(cè)這里的type字段,就很容易知道該節(jié)點(diǎn)的類型以及其實(shí)際存儲(chǔ)數(shù)據(jù)的字段了。其它的字段是什么意思呢?cJSON.h文件里的注釋說(shuō)的很明白了,valueint,valuedouble以及valuestring保存的是相應(yīng)的值,string存放的是本字段的名字。
? ? ? ???接下來(lái)分析程序的執(zhí)行過(guò)程,編譯參數(shù)加上-g,使用gdb調(diào)試程序,畫出整個(gè)構(gòu)造過(guò)程的函數(shù)調(diào)用圖。具體的調(diào)試過(guò)程就不細(xì)說(shuō)了,我撿一些關(guān)鍵點(diǎn)說(shuō)說(shuō):
? ? ? ? 調(diào)試過(guò)程中,我們發(fā)現(xiàn)?cJSON_AddStringToObject()?等其實(shí)是宏定義,本質(zhì)上調(diào)用的都是?cJSON_AddItemToObject()?函數(shù),在cJSON.h文件中可以看到如下定義:
| 1 2 3 4 5 6 | #define cJSON_AddNullToObject(object,name)??????cJSON_AddItemToObject(object, name, cJSON_CreateNull()) #define cJSON_AddTrueToObject(object,name)??????cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) #define cJSON_AddFalseToObject(object,name)???? cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) #define cJSON_AddBoolToObject(object,name,b)????cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) #define cJSON_AddNumberToObject(object,name,n)??cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) #define cJSON_AddStringToObject(object,name,s)??cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) |
? ? ? ? 另外?cJSON_CreateNull()?等函數(shù)都是調(diào)用?cJSON_New_Item()?函數(shù)申請(qǐng)到初始化為0的空間構(gòu)造相關(guān)的節(jié)點(diǎn)信息。構(gòu)造過(guò)程中的函數(shù)調(diào)用圖如下:
? ? ? ???構(gòu)造的Json字符串最終在內(nèi)存中形成的結(jié)構(gòu)如下圖所示:
? ? ? ? 構(gòu)造過(guò)程相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,數(shù)組類型這里沒(méi)有涉及到,但是分析起來(lái)也很簡(jiǎn)單。
? ? ? ???我們最后調(diào)用?cJSON_Print()?函數(shù)生成這個(gè)結(jié)構(gòu)所對(duì)應(yīng)的字符串。生成說(shuō)起來(lái)容易,遍歷起整個(gè)結(jié)構(gòu)并進(jìn)行字符串格式控制卻比較繁瑣。這里相關(guān)的代碼還有遞歸清理這個(gè)內(nèi)存結(jié)構(gòu)的函數(shù)不再贅述,有興趣的同學(xué)請(qǐng)自行研究。
? ? ? ???構(gòu)造的過(guò)程我們就說(shuō)到這里,明天我們研究下解析的過(guò)程。
? ? ? ???昨天簡(jiǎn)單的分析了一下cJSON對(duì)Json格式的構(gòu)造過(guò)程,今天仔細(xì)讀了讀README文件,發(fā)現(xiàn)README其實(shí)說(shuō)的已經(jīng)很詳細(xì)了。重復(fù)造輪子就重復(fù)造輪子吧,今天我們?cè)僖黄鸱治鼋馕龅倪^(guò)程。
? ? ? ???繼續(xù)用之前構(gòu)造的Json格式來(lái)進(jìn)行解析,之前分析構(gòu)造函數(shù)的時(shí)候,我們只是簡(jiǎn)單的分析了幾個(gè)cJSON結(jié)構(gòu)的構(gòu)造過(guò)程,并沒(méi)有涉及到各種類型的數(shù)組等構(gòu)造。因?yàn)槲矣X(jué)得理解了一般的構(gòu)造過(guò)程,更復(fù)雜的類型自己再簡(jiǎn)單看看源碼,畫畫圖就很容易理解。
? ? ? ????學(xué)習(xí)一個(gè)事物一定要先抓住主線,先掌握一個(gè)事物最常用的那50%,其他的邊邊角角完全可以留給實(shí)踐去零敲碎打(孟巖語(yǔ))。
閑話打住,先上一段解析使用的代碼:
| 12345678910111213141516171819202122232425262728293031 | #include <stdio.h> #include <stdlib.h> #include "cJSON.h" int?main?(?int?argc?,?char?*?argv?[?]?) {char?*?text?=?"{\"name\": \"Jack (\\\"Bee\\\") Nimble\", " ?????????????????????????"\"format\": {\"type\": \"rect\", " ?????????????????????????"\"width\": 1920, \"interlace\": false}}"?; cJSON?*?root?=?cJSON_Parse?(?text?)?; if?(?!?root?)?{printf?(?"Error before: [%s]\n"?,?cJSON_GetErrorPtr?(?)?)?; return?EXIT_FAILURE?; }char?*?out?=?cJSON_Print?(?root?)?; printf?(?"text:\n%s\n\n"?,?out?)?; free?(?out?)?;char?*?name?=?cJSON_GetObjectItem?(?root?,?"name"?)?->?valuestring?; printf?(?"name : %s\n"?,?name?)?; cJSON?*?format?=?cJSON_GetObjectItem?(?root?,?"format"?)?; int?width?=?cJSON_GetObjectItem?(?format?,?"width"?)?->?valueint?; printf?(?"width : %d\n"?,?width?)?; cJSON_Delete?(?root?)?; return?EXIT_SUCCESS?; } |
? ? ? ???程序運(yùn)行輸出:
| 1 2 3 4 5 6 7 8 9 10 11 12 | text?: { "name"?:?"Jack (\"Bee\") Nimble"?, "format"?:?{ "type"?:?"rect"?, "width"?:?1920?,"interlace"?:?false } }name?:?Jack?(?"Bee"?)?Nimble width?:?1920 |
? ? ? ???從這段代碼中可以看到,解析過(guò)程就?cJSON_Parse()?一個(gè)接口,調(diào)用成功返回cJSON結(jié)構(gòu)體的指針,錯(cuò)誤返回NULL,此時(shí)調(diào)用?cJSON_GetErrorPtr()?可以得要錯(cuò)誤原因的描述字符串。
? ? ? ???查看?cJSON_GetErrorPtr()?的源碼可以得知,其實(shí)錯(cuò)誤信息就保存在全局字符串指針ep里。
? ? ? ???關(guān)鍵就是對(duì)?cJSON_Parse()?過(guò)程的分析了,我們帶參數(shù)-g重新編譯代碼并下斷點(diǎn)開(kāi)始調(diào)試跟蹤。
? ? ? ???首先?cJSON_Parse()?調(diào)用?cJSON_New_Item()?申請(qǐng)一個(gè)新的cJSON節(jié)點(diǎn),然后使用函數(shù)對(duì)輸入字符串進(jìn)行解析(中間使用了?skip()?函數(shù)來(lái)跳過(guò)空格和換行符等字符)。
? ? ? ???parse_value()?函數(shù)對(duì)輸入字符串進(jìn)行匹配和解析,檢測(cè)輸入數(shù)據(jù)的類型并調(diào)用?parse_string()?、?parse_number()?、?parse_array()?、?parse_object()?等函數(shù)進(jìn)行解析,然后返回結(jié)束的位置。
? ? ? ???函數(shù)調(diào)用的關(guān)系如下圖:
? ? ? ???這些函數(shù)之間相互調(diào)用,傳遞待解析的字符串直到結(jié)束或者遇見(jiàn)錯(cuò)誤便返回,最后會(huì)構(gòu)建出一個(gè)和之前結(jié)構(gòu)一樣的Json內(nèi)存結(jié)構(gòu)來(lái),解析的過(guò)程就完成了。檢索過(guò)程很簡(jiǎn)單?cJSON_GetObjectItem()?函數(shù)負(fù)責(zé)進(jìn)行某個(gè)對(duì)象的自成員的名字比對(duì)和指針的返回。不過(guò)要注意這里采用了?cJSON_strcasecmp()?這個(gè)無(wú)視大小寫的字符串比較函數(shù),因?yàn)镴son格式的鍵值對(duì)的名稱不區(qū)分大小寫。
? ? ? ? 這樣cJSON庫(kù)的整個(gè)構(gòu)建和解析過(guò)程的主干內(nèi)容就總結(jié)出來(lái)了,剩下的邊邊角角可以在這個(gè)主線分析結(jié)束之后再繼續(xù)下去,比如Json格式化,解析出來(lái)的內(nèi)存結(jié)構(gòu)復(fù)制,從這個(gè)內(nèi)存結(jié)構(gòu)解析出字符串以及這個(gè)內(nèi)存結(jié)構(gòu)的遞歸刪除等等留給大家自己進(jìn)行吧。
? ? ? ???P.S.?cJSON_InitHooks()?這個(gè)函數(shù)不過(guò)是cJSON允許用戶使用其它的內(nèi)存申請(qǐng)和釋放函數(shù)罷了(默認(rèn)是malloc和free),另外啰嗦一下,這個(gè)接口也可以用來(lái)檢測(cè)內(nèi)存泄露。只要實(shí)現(xiàn)malloc和free的包裝函數(shù),在其中統(tǒng)計(jì)和打印內(nèi)存申請(qǐng)釋放操作就可以了。
總結(jié)
以上是生活随笔為你收集整理的cJSON库源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android状态机实现原理
- 下一篇: Redis源码简要分析