草庐IT

c++ - asm.js-如何实现函数指针

coder 2024-02-21 原文

注意:这个问题纯粹是关于asm.js的,而不是关于C++或任何其他编程语言的。

标题已经说过:

如何有效地实现函数指针?

我在网上找不到任何东西,所以我想在这里问。

编辑:
我想在正在使用的编译器中实现虚函数。

在C++中,我将执行以下操作来生成vtable:

#include <iostream>

class Base {
  public:
    virtual void doSomething() = 0;
};

class Derived : public Base {
  public:
    void doSomething() {
        std::cout << "I'm doing something..." << std::endl;
    }
};

int main()
{
    Base* instance = new Derived();
    instance->doSomething();
    return 0;
}

更准确地说;如何在不需要纯JavaScript的情况下在asm.js中生成vtable
无论如何,我都希望在使用函数指针时使用asm.js的“近本机”功能。

该解决方案可能仅适用于的计算机生成代码。

最佳答案

查看asm.js的工作原理,我认为最好的选择是使用原始CFront编译器使用的方法:将虚拟方法编译为采用this指针的函数,并在通过它之前使用thunk纠正this指针。我将逐步介绍它:

无继承

将方法简化为特殊功能:

void ExampleObject::foo( void );

会变成
void exampleobject_foo( ExampleObject* this );

这对于基于非继承的对象很好。

单继承

我们可以通过一个简单的技巧轻松添加对任意大量单继承的支持:始终将对象始终存储在内存库中:
class A : public B

在内存中将变为:
[[ B ] A ]

越来越近!

多重继承
现在,多重继承使得处理起来更加困难
class A : public B, public C

B和C都不可能在A的开头。他们根本无法共存。有两种选择:
  • 为每个对base的调用存储一个显式偏移量(称为增量)。
  • 不允许通过A到B或C的 call

  • 由于多种原因,第二种选择更为可取。如果您要调用基类成员函数,那么很少需要通过派生类来实现。相反,您可以简单地转到C::conlyfunc,然后可以为您免费调整指针。允许A::conlyfunc会删除编译器本可以使用的重要信息,而几乎没有好处。

    首选使用C++。所有多个继承对象在每次调用基类之前都会调用thunk,这将调整this指针指向其内部的子对象。在一个简单的示例中:
    class ExampleBaseClass
    {
        void foo( void );
    }
    
    class ExampleDerivedClass : public ExampleBaseClass, private IrrelevantBaseClass
    {
        void bar( void );
    }
    

    然后将成为
    void examplebaseclass_foo( ExampleBaseClass* this );
    void examplederivedclass_bar( ExampleDerivedClass* this);
    
    void examplederivedclass_thunk_foo( ExampleDerivedClass* this)
    {
        examplebaseclass_foo( this + delta );
    }
    

    在许多情况下都可以内联,因此开销不会太大。但是,如果您永远都不能将ExampleBaseClass::foo称为ExampleDerivedClass::foo,则不需要这些重击,因为可以很容易地从调用本身中识别出增量。

    虚拟函数

    虚拟功能增加了一层全新的复杂性。在多重继承的例子中,thunk具有固定的地址可调用;我们只是在调整this之前将其传递给已知函数。对于虚函数,我们正在调用的函数是未知的。我们可能会被在编译时无法得知的派生对象所覆盖,因为它位于另一个翻译单元或库等中。

    这意味着我们需要为每个实际上具有可重写功能的对象提供某种形式的动态分配。许多方法都是可行的,但是C++实现倾向于使用简单的函数指针数组或vtable。对于每个具有虚函数的对象,我们将一个点作为隐藏成员添加到数组中,通常在前面:
    class A
    {
    hidden:
        void* vtable;
    public:
        virtual void foo( void );
    }
    

    我们添加了重定向到vtable的thunk函数
    void a_foo( A* this )
    {
        int vindex = 0;
        this->vtable[vindex](this);
    }
    

    然后,用指向我们实际要调用的函数的指针填充vtable:

    vtable [0] =&A::foo_default; //我们对foo的基类实现

    在派生类中,如果我们想覆盖此虚函数,我们要做的就是在我们自己的对象中更改vtable,以指向新函数,并且它也会在基类中覆盖:
    class B: public A
    {
        virtual void foo( void );
    }
    

    然后将在构造函数中执行此操作:
    ((A*)this)->vtable[0] = &B::foo;
    

    最后,我们支持所有形式的继承!
    几乎。

    虚拟继承
    这个实现有一个最后的警告:如果您继续允许在真正的意思是Base::foo时使用Derived::foo,则会遇到菱形继承(钻石问题):
    class A : public B, public C;
    class B : public D;
    class C : public D;
    
    A::DFunc(); // Which D?
    

    当您将基类用作有状态类时,或者放置应具有has-a而不是is-a的函数时,也会发生此问题。通常,这表明需要进行重组。但不总是。

    在C++中,它的解决方案不是很优雅,但是可以起作用:
    class A : public B, public C;
    class B : virtual D;
    class C : virtual D;
    

    这要求实现此类类和层次结构的人员提前考虑并有意使它们的类变慢一些,以支持将来可能的用法。但这确实解决了问题。

    我们如何实现该解决方案?
    [ [ D ] [ B ] [ Dptr ] [ C ] [ Dptr ] A ]
    

    与正常继承中的直接继承不同,我们没有通过虚拟继承直接使用基类,而是通过指针推送了D的所有用法,增加了一个间接性,同时将多个实例化为一个实例。注意,B和C都有自己的指针,它们都指向同一个D。这是因为B和C不知道它们是自由 float 副本还是绑定(bind)在派生对象中。两者都需要使用相同的调用,否则虚拟函数将无法正常工作。

    摘要
  • 在基类
  • 中使用特殊的this参数将方法调用转换为函数调用
  • 在内存中构造对象,因此单继承与无继承没有区别
  • 添加thunk以调整此指针,然后为多个继承调用基类
  • 将vtables添加到具有虚拟方法的类中,并使对方法的所有调用都通过vtable传递给方法(thunk-> vtable-> method)
  • 通过指向基础对象的指针处理虚拟继承,而不是派生对象调用

  • 所有这些在js.asm中都很简单。

    关于c++ - asm.js-如何实现函数指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20681758/

    有关c++ - asm.js-如何实现函数指针的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

      给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

    5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

      我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

    7. ruby - 如何指定 Rack 处理程序 - 2

      Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

    8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

      在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

    9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

      我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

    10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

      我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

    随机推荐