中关村村草 发表于 2011-07-21 13:35

长跳破坏 RAII 的简单例子

转:night_stalker

长跳破坏 RAII 的简单例子


Resource Acquisition Is Initialization 是 C++ 卖点之一, 简单来说就是退出局部作用域的时候局部变量的析构函数就会被自动调用. 在 RAII 特性上可以做自动指针, 可以实现类似 GC 的功能, STL 和 Boost 都有智能指针和自动指针的实现.

但是如果作用域里调用的函数用了长跳转跳出去了, 析构函数就不会被调用, 内存泄漏就由此而起, 而且 ... 长跳转其实在 Ruby 里到处都是 ... 例如:


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

C++代码#include "iostream"
#include "ruby.h"

namespace {
// 典型的 C++ 类: 在构造函数分配资源, 在析构函数销毁资源
struct C {
    int *a;
    C() {
      a = new int;
      std::cout << "constructor called\n";
    }
    ~C() {
      delete[] a;
      std::cout << "destructor called\n";
    }
};

VALUE f(VALUE self) {
    // 构造函数被调用
    C c;
    // block 参数
    VALUE args;
    // 如果 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)

Console代码ruby -rmkmf -e "create_makefile 'ra'"
make测试长跳 (同一目录下的 test.rb)

Ruby代码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 就泄漏了

Console代码→ ruby -v
ruby 1.9.3dev (2011-07-10 trunk 32499)
→ ruby test.rb
RAII broken:
constructor called

RAII normal:
constructor called
destructor called
补充:
GCC 的 cleanup attribute 扩展也是 RAII, 还没时间试验, 估计也是 ...

2gua 发表于 2011-07-21 22:46

支持村艹了。

2gua 发表于 2011-07-23 18:28

但这个应该不太用得上。

bugbugbug3 发表于 2011-08-01 23:00

主要是用C++来写扩展的时候,要注意。
本来,用C++编程时,是一直不建议和setjmp,longjmp一起使用的。因为setjmp和longjmp不支持 C++的对象语义。

2gua 发表于 2011-08-02 08:28

村艹咋不发了?
页: [1]
查看完整版本: 长跳破坏 RAII 的简单例子