【问题标题】:Calling Ruby function from C++ in a loop results in "stack level too deep"在循环中从 C++ 调用 Ruby 函数会导致“堆栈级别太深”
【发布时间】:2010-11-05 06:37:27
【问题描述】:

我正在尝试从 C++ 运行 Ruby 代码块。我有两个 Ruby 函数,一个叫做 Init(),一个叫做 Loop()。我遇到的问题是,在从 SystemStackError 获得“堆栈级别太深”之前,我只能 Loop() 这么多次。据我所知,我的 Ruby 代码不是递归的。如您所见,到目前为止,这段 Ruby 代码仅用于概念验证,只加载了调试风格的内容并在面板上闪烁。这是 Ruby 代码:

def Init()
    puts 'Hello from script\'s Init()!'
    $i = 0
    $p = Panel.new
    $p.Debug
    $p.Extinguish( "Running" )
    $p.Illuminate( "Fault" )
end

def Loop()
    puts 'Hello from Loop!' + $i.to_s
    $i += 1
    puts $p
    $p.Debug
    $p.Illuminate( "Running" ) if $i % 2 == 1
    $p.Extinguish( "Running" ) if $i % 2 != 1
end

我在 C++ 中的 Panel 实现是:

ruby_init();
VALUE cPanel;
cPanel = rb_define_class( "Panel", rb_cObject );
rb_define_singleton_method( cPanel, "new", (RubyMethod*)&StaticRubyNew, 0 );
rb_define_method( cPanel, "Debug", (RubyMethod*)&StaticRubyDebug, 0 );
rb_define_method( cPanel, "Extinguish", (RubyMethod*)&StaticRubyExtinguish, 1 );
rb_define_method( cPanel, "Illuminate", (RubyMethod*)&StaticRubyIlluminate, 1 );

我调用脚本函数如下:

rb_eval_string( program );

rb_funcall( Qnil, rb_intern( "Init" ), 0, NULL );

// In a 200ms loop:
rb_funcall( Qnil, rb_intern( "Loop" ), 0, NULL );

在我编写了 new 的(可疑)实现之前,没有任何效果:

VALUE MainWidget::RubyNew( VALUE clas )
{
    // Looks like we have to return *something* instead of Qnil, even if I
    // don't have anything to wrap yet.
    const char* s = "Dude";
    VALUE tdata = Data_Wrap_Struct( clas, StaticRubyMark, StaticRubyFree, const_cast<char*>(s) );
    return tdata;
}

RubyMark 和 RubyFree 不做任何事情,而 RubyDebug、RubyIlluminate 等对于手头的问题也没有做任何值得注意的事情。

我尝试将 Init 和 Loop 作为类方法包装在一个类中,这样我就可以使用真正的接收器调用 rb_funcall()。我尝试通过调用 rb_protect() 来获取回溯(回溯显示为空)。网上似乎没有将脚本加载为字符串的秘密,所以 rb_eval_string() 是一个猜测。 rb_load_file() 也不起作用。

为什么这会导致堆栈问题?我可以编辑我的 Ruby 脚本,添加或删除代码,并且在执行不同数量的循环后堆栈会爆炸。我可以执行的循环数与行数没有明显的关系。如果我删除一行,我可能会得到 45 个循环。如果我删除另一个,我可能会得到超过 2000。我做错了什么?

根据下面的响应添加一些代码——这是为 Ruby API 调用(需要 C 风格的函数)提供 C++ 方法:

typedef VALUE (RubyMethod)(...);
extern "C" /*static*/ VALUE StaticRubyNew( VALUE self )
{
    return MainWidget::M_this->RubyNew( self );
}

【问题讨论】:

  • 好吧,我更接近一点——我将所有这些都提取到了一个纯 C 驱动的应用程序中,它运行了至少 500,000 次循环(在我杀死它之前)。这一定是我在使用 C++ 到 C 接口时做错了什么。

标签: c++ ruby


【解决方案1】:

好的,所以我削减了它,直到我得到一个类似于我的 C 实现的工作版本,但问题与 C++ 与 C 无关。上面没有显示的(因为我认为它不相关)是这是一个 Qt 应用程序,我有两个插槽——一个用于初始化 ruby​​ 并加载程序,另一个用于调用循环函数。后一个插槽从计时器重复调用(不相关)。当我将 ruby​​_init() 移出插槽并移入 main() 时,突破发生了。谷歌搜索发现了这个有趣的answer

来自 Matz 本人,“任何 Ruby 对象都不应从低于 ruby_init() 调用时的位置。"

所以发生的情况是,当每个插槽被调用时,它们在堆栈上的不确定位置,而如果你从 main() 调用 ruby​​_init() 然后开始运行 Qt 的事件循环,你肯定会在堆栈上的正确位置来执行 rb_funcall() 等。

【讨论】:

  • 顺便说一句,有人知道为什么 Ruby 有这个规则吗?为什么相对于其他调用的堆栈位置如此重要?你如何编写依赖于相对于其他调用的堆栈位置的代码(记住 ruby​​_init() 在调用 rb_funcall 之前返回,所以它们不应该同时在堆栈上)?
  • 稍后在您引用的线程中:“Ruby 需要知道堆栈的大小和位置,以便 GC 可以标记堆栈上的对象。”和“Ruby 需要知道 C 堆栈的位置,以便对其进行标记,否则扩展会中断。”他们是否将 C 堆栈与 Ruby 堆栈混合在一起?这让我觉得这是一种不必要的聪明尝试,也可能是虚假的懒惰。
猜你喜欢
  • 2013-10-05
  • 2012-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多