- 论坛徽章:
- 0
|
转: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[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)
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) [x86_64-darwin10.8.0]
- → ruby test.rb
- RAII broken:
- constructor called
- RAII normal:
- constructor called
- destructor called
复制代码 补充:
GCC 的 cleanup attribute 扩展也是 RAII, 还没时间试验, 估计也是 ... |
|