Home

C语言中的生存期问题

23 Mar 2014 by LelouchHe

问题的来源

前些日子在实现自己的JSON库,使用的是惯用的C语言.但写到最后,发现越写越像C++的风格了.虽然这并不是我的初衷,但可能这反应了一些我平时没有注意到的问题

现在看来,其中之一就是生存期的问题

什么是生存期

生存期,就是指一个对象从生到死的这段时间.生,意味着给运行系统给这个对象分配内存,初始化值;死,意味着系统收回了对象的内存,对象不再有意义

有GC的语言,比较不关心这个问题,使用者一般只负责对象的创建,然后就可以随意的使用,运行系统的GC保证了只有当对象无法被使用时,才会进行释放和销毁.

C++虽然没有GC,但是RAII惯用法提供了比GC效率更高,更优雅的解决问题的方法,配合标准库里的智能指针,就同某位牛人说过的那样,”再也不会有内存问题的困扰了”

反观C,缺少运行系统和编译器的辅助,试图很方便的解决对象生存死亡的问题,确实有些难度

难点在哪里

C是比较初级的语言了(谁说它是高级语言来着!!),代码和运行时候机器码基本就是一一对应的了(忽略编译器的优化和CPU的执行),也就是说,除非我们写出来,否则它就不会运行

以前不知道在哪里看到这句话时,觉得很平常,但在实践过很长时间之后,终于发现这点确实点出了C语言处理问题的难点了,生存期问题只是这个问题的一个表现而已

比如,在skip_list库的实现中,需要通用类型key/value,在C语言中,只能是void *类型的指针,通过api接口调用时,指向调用者给予的内存位置.但由谁来保证这些指针指向的对象,在skip_list还在使用的时候,一直存活的呢?库的内部实现是否要针对key/value的存活与否进行额外的校验和判断呢?如何进行校验和判断呢?

其实概括来说,难点有2个方向:如果试图完全控制对象的生存期,我们就面临着对象类型的不可知;如果试图完成对象的共享,我们就面临如何共享和一个大lib的负担

完全控制生存期

完全控制生存期,只是将带操作的对象完全置于我们的控制范围之内,类似于实现了对象的值语义.当对象进入操作之后,首先是复制对象,然后后续的操作都在这个复制的对象之上,当整个操作结束后,再将新建对象释放掉.此时,原先对象的死活就和我们没有任何关系了

但这样做有几个问题:

关于第二个问题,是下面几点缺乏造成的:

完全共享对象

共享对象,就是不进行值语义的转换,简单的使用指针引用对象.这样的操作代价最小,但问题同样很多:

解决的思路

可以看到,除去系统实现本身要满足的各种规格,值语义的问题主要在于C语言的类型体系,而共享,则涉及到了整个系统的实现问题(即我们无法单独实现共享,而不告知其他人)

解决的思路就是从这两点上入手.先是判断好系统要求的规格限制和要求,然后按照这两点进行突破.下面提供一些我能想到的思路

值语义

C的类型体系是没有多少办法绕过去的,所以值语义的问题很难解决:

可以看到,其实STL就是实现的值语义的库,操作类型的最低要求就是可复制.

有的时候我们自作聪明,将裸指针传进去来避免复制开销,最后出了bug,反过来埋怨STL真难用.真是,too young, too simple, sometimes naive

共享对象

共享对象涉及到的是系统问题,一旦决定共享,一定预先设计好,这个共享层面到哪个层次结束.避免底层的实现机制干扰了整个系统的设计实现

关于C++

上面提了很多建议,最后都是说要转向C++.这里并没有说C++有多么的好,只是目前我们遇到的这个问题,C++方面已经解决的很不错了,语言层面和编译器层面,大量的牛人已经给出了非常多的辅助,如果单是因为语言层面的内心纠结而不去使用,真的是愧对牛人们的辛劳

在我看来,C++最赞的一点就是,各个功能的正交分离.如果没有用到某个东西,它很少能成为你的负担.

像我,就愿意把C++看做是个大的功能包,里面有RAII,有STL,有智能指针,有function/bind(比函数指针更通用化),我觉得完全足够我平时的开发了.这些大部分都属于泛型一边,只有RAII涉及到最初级的面向对象,而此处也是当做工具来使用的

用C++来开发,如果对接口有洁癖(比如我),最后用C封装一下,不就两全其美了么?