JSON

从零开始的JSON库教程(五):解析数组解答篇(2)

字号+ 作者:H5之家 来源:H5之家 2016-10-20 14:00 我要评论( )

但对于数组,我们应该先把数组内的元素通过递归调用 lept_free() 释放,然后才释放本身的 v-u.a.e: void lept_free(lept_value* v) {size_t i;assert(v != NULL);switch (v-type) {case LEPT_STRING:free(v-u. s .

但对于数组,我们应该先把数组内的元素通过递归调用 lept_free() 释放,然后才释放本身的 v->u.a.e:

void lept_free(lept_value* v) { size_t i; assert(v != NULL); switch (v->type) { case LEPT_STRING: free(v->u.s.s); break; case LEPT_ARRAY: for (i = 0; i < v->u.a.size; i++) lept_free(&v->u.a.e[i]); free(v->u.a.e); break; default: break; } v->type = LEPT_NULL; }

修改之后,再运行内存泄漏检测工具,确保问题已被修正。

4. 解析错误时的内存处理

遇到解析错误时,我们可能在之前已压入了一些值在自定议堆栈上。如果没有处理,最后会在 lept_parse() 中发现堆栈上还有一些值,做成断言失败。所以,遇到解析错误时,我们必须弹出并释放那些值。

在 lept_parse_array 中,原本遇到解析失败时,会直接返回错误码。我们把它改为 break 离开循环,在循环结束后的地方用 lept_free() 释放从堆栈弹出的值,然后才返回错误码:

static int lept_parse_array(lept_context* c, lept_value* v) { /* ... */ for (;;) { /* ... */ if ((ret = lept_parse_value(c, &e)) != LEPT_PARSE_OK) break; ->json == ',') { /* ... */ } ->json == ']') { /* ... */ } else { ret = LEPT_PARSE_MISS_COMMA_OR_SQUARE_BRACKET; break; } } /* Pop and free values on the stack */ for (i = 0; i < size; i++) lept_free((lept_value*)lept_context_pop(c, sizeof(lept_value))); return ret; } 5. bug 的解释

这个 bug 源于压栈时,会获得一个指针 e,指向从堆栈分配到的空间:

for (;;) { /* bug! */ lept_value* e = lept_context_push(c, sizeof(lept_value)); lept_init(e); size++; if ((ret = lept_parse_value(c, e)) != LEPT_PARSE_OK) return ret; /* ... */ }

然后,我们把这个指针调用 lept_parse_value(c, e),这里会出现问题,因为 lept_parse_value() 及之下的函数都需要调用 lept_context_push(),而 lept_context_push() 在发现栈满了的时候会用 realloc() 扩容。这时候,我们上层的e 就会失效,变成一个悬挂指针(dangling pointer),而且 lept_parse_value(c, e) 会通过这个指针写入解析结果,造成非法访问。

在使用 C++ 容器时,也会遇到类似的问题。从容器中取得的迭代器(iterator)后,如果改动容器内容,之前的迭代器会失效。这里的悬挂指针问题也是相同的。

但这种 bug 有时可能在简单测试中不能自动发现,因为问题只有堆栈满了才会出现。从测试的角度看,我们需要一些压力测试(stress test),测试更大更复杂的数据。但从编程的角度看,我们要谨慎考虑变量的生命周期,尽量从编程阶段避免出现问题。例如把 lept_context_push() 的 API 改为:

static void lept_context_push( lept_context* c, const void* data, size_t size);

这样就确把数据压入栈内,避免了返回指针的生命周期问题。但我们之后会发现,原来的 API 设计在一些情况会更方便一些,例如在把字符串值转化(stringify)为 JSON 时,我们可以预先在堆栈分配字符串所需的最大空间,而当时是未有数据填充进去的。

无论如何,我们编程时都要考虑清楚变量的生命周期,特别是指针的生命周期。

6. 总结

经过对数组的解析,我们也了解到如何利用递归处理复合型的数据类型解析。与一些用链表或自动扩展的动态数组的实现比较,我们利用了自定义堆栈作为缓冲区,能分配最紧凑的数组作存储之用,会比其他实现更省内存。我们完成了数组类型后,只余下对象类型了。

分享给小伙伴们:

本文标签: JSON,数组/">JSON,数组

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

  • 本类最热新闻

  •  

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    相关文章
    • JSON解析和XML解析对比,JSON解析XML解析

      JSON解析和XML解析对比,JSON解析XML解析

      2016-10-21 11:01

    • 使用Couchbase存储Non-JSON数据

      使用Couchbase存储Non-JSON数据

      2016-10-18 16:01

    • 利用Ajax和JSON技术实现对数据库的异步操作

      利用Ajax和JSON技术实现对数据库的异步操作

      2016-10-18 15:00

    • Json .NET操作

      Json .NET操作

      2016-10-18 14:00

    网友点评
    p