思考一:
任何一个程序,只要不是 helloworld 那样的简单程序,必然会用到内存管理。内存管理是写程序不可避免的过程。C程序员最大的恶魔就是野指针和内存泄漏。这是每个C程序员的噩梦。C++继承了C的缺陷,基本上半斤八两。而且C++还可以自由重载new操作符,给内存管理更加重了复杂性。 我常常在想,写BASH脚本的时候,我们有管理过内存么?即便是java那样的语言,内存管理也是后台进行的,并不是可避免的,俗称GC。可是写脚本的时候,真的完全没用内存管理方面的困扰。
到底是哪里出了问题呢?到底为何脚本就不用管理内存?
思考二:
能不能完全不依靠malloc/free new/delete 编写出一个 c/c++程序呢?
结果是显然的,能!
但是只有对 hello world 那样的程序才有用。最近的一项小随笔项目证实了我的想法 https://github.com/microcai/hm 。在这个程序里,我没用使用任何 malloc/free new/delete 来管理内存。
为何这个程序可以不用内存管理呢? 于是在另一个随笔项目里,我依然使用了内存管理,虽然是智能指针自动管理的,但是毕竟使用了 new 操作 https://github.com/microcai/googleproxy 。我在想,凭啥这个程序就必须使用 new 了?
思考三:
程序,说到底就是一个状态机。在 hm 程序里,我采用的是“过程化”编程,外加同步多进程的IO模型。在 googleproxy 程序里,使用的是单线程异步IO的模型。
hm里,状态机的状态就是进程的状态。一步一步执行下去,状态随之切换。cpu执行到哪一行,哪一行就是当前状态。
googleproxy里,情况有了变化,因为使用了异步,所有的状态变化都是围绕一个中心进行的: boost::io_service::run() 。 在 run() 里,通过回调来通知状态的变化。也就是说,cpu执行到哪个回调,哪个就是当前状态。 区别是什么:
在不同的状态之间,我们有数据要共享! hm 多使用局部变量,不同的状态需要共享数据,通常也处于同一代码层级!可以直接引用需要的数据!
googleproxy 里,不同的状态之间,是不同的 run() 操作的回调,需要共享数据,但是上一个状态的局部变量已经消失!上一个状态的局部变量已经消失! 要跨栈域引用共享数据,唯一的办法就是将数据创建在堆上!
简单的来说,在一个程序里,所有代码的执行路径可以归纳为从 main() 开始的一个调用树。一个函数只能引用本层和上层的局部变量,无法引用子层和兄弟层和兄弟的子层的局部变量。因为这些地方的变量都是不存在的呀!
所以,只能将共享数据创建在堆上。这样才能跨过调用树的生存周期!
这也是唯一需要堆管理的理由。
如果一个变量要进入函数的时候创建在堆上,退出的时候释放,通常的做法其实就是使用栈变量,或者是使用可变长度的stl容器。
如果返回值是对象,也不要使用指针了,直接返回对象吧!别担心临时对象拷贝开销了。
只有当跨栈域共享对象的时候,才考虑使用堆吧! 记住:即便如此,也不要使用裸指针。裸指针只用来进行直接内存访问(底层编程的时候),千万别用来引用对象。使用智能指针吧!放弃对象的拷贝开销的担心吧! 那几百个周期的拷贝操作比 new/delete 的内存管理代码的开销相比,还是低太多里。何况有了 c++11 的 Move 语义,很多时候已经没用拷贝开销里。
放心大胆的脚本化C++程序吧! 实在需要跨栈共享数据的时候,使用 shared_ptr 引用吧!
Comments