Resource Acquisition Is Initialization 是 C++ 卖点之一, 简单来说就是退出局部作用域的时候局部变量的
析构函数就会被自动调用. 在 RAII 特性上可以做自动指针, 可以实现类似 GC 的功能, STL 和 Boost 都有
智能指针和自动指针的实现.
但是如果作用域里调用的函数用了长跳转跳出去了, 析构函数就不会被调用,
内存泄漏就由此而起, 而且 ... 长跳转其实在 Ruby 里到处都是 ... 例如:
def find_first_a list
list.each {|i| return 'a' if i == a } # return in proc => long jump!
return false
end
下面用一个简单的 extension 验证看似正确的代码是怎么内存泄漏的 ...
ra.cpp
#include "iostream"
#include "ruby.h"
namespace {
// 典型的 C++ 类: 在构造函数分配资源, 在析构函数销毁资源
struct C {
int *a;
C() {
a = new int[100];
std::cout << "constructor called\n";
}
~C() {
delete[] a;
std::cout << "destructor called\n";
}
};
VALUE f(VALUE self) {
// 构造函数被调用
C c;
// block 参数
VALUE args[0];
// 如果 block 中用了 return 产生了长跳转, 下面的代码都不会被调用
rb_yield((VALUE)args);
return Qnil;
// RAII 隐藏了在这里调用 c 的析构函数的代码
}
}
extern "C"
void Init_ra() {
VALUE c = rb_define_class("C", rb_cObject); // 定义 Ruby 类 C
rb_define_method(c, "f", RUBY_METHOD_FUNC(f), 0); // 定义 Ruby 方法 C#f
}
编译生成扩展库 (ra.so 或 ra.bundle)
ruby -rmkmf -e "create_makefile 'ra'"
make
测试长跳 (同一目录下的 test.rb)
require_relative "ra"
def raii_broken
C.new.f {
return # long jump !
}
end
def raii_normal
C.new.f {}
end
puts "RAII broken:"
raii_broken
puts "\nRAII normal:"
raii_normal
结果: 不用 return 就没泄漏, 用 return 就泄漏了
→ ruby -v
ruby 1.9.3dev (2011-07-10 trunk 32499) [x86_64-darwin10.8.0]
→ ruby test.rb
RAII broken:
constructor called
RAII normal:
constructor called
destructor called
补充:
GCC 的 cleanup attribute 扩展也是 RAII, 还没时间试验, 估计也是 ...