首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >asm.js -如何实现函数指针

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

Stack Overflow用户
提问于 2013-12-19 12:25:12
回答 3查看 855关注 0票数 4

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

正如标题已经说过的:

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

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

编辑:,我想在我正在做的编译器中实现虚拟函数。

在C++中,我会这样做来生成一个vtable

代码语言:javascript
复制
#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;
}

更准确地说,如何在不需要普通vtable的情况下在asm.js中生成JavaScript?在任何情况下,我都希望asm.js在使用函数指针时具有“近本地”功能。

该解决方案适用于计算机生成的代码只适用于

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-12-30 08:16:13

查看asm.js的工作原理,我认为最好的方法是使用原始CFront编译器使用的方法:将虚拟方法编译到接受此指针的函数,并在传递该指针之前使用块更正该指针。我会一步一步地看一遍:

无继承

将方法简化为特殊功能:

代码语言:javascript
复制
void ExampleObject::foo( void );

会转化成

代码语言:javascript
复制
void exampleobject_foo( ExampleObject* this );

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

单继承

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

代码语言:javascript
复制
class A : public B

在记忆中会变成:

代码语言:javascript
复制
[[ B ] A ]

越来越近了!

多重继承现在,多重继承使得使用它更加困难。

代码语言:javascript
复制
class A : public B, public C

B和C都不可能出现在A的开头,它们根本不可能共存。有两种选择:

  1. 为每个基类调用存储显式偏移量(称为增量)。
  2. 不允许通过A到B或C的呼叫

出于各种原因,第二种选择更为可取;如果要调用基类成员函数,则很少需要通过派生类来进行选择。相反,您可以简单地转到C::conlyfunc,然后它可以免费地为您调整指针。允许A::conlyfunc删除编译器本来可以使用的重要信息,但没有什么好处。

第一个选择在C++中使用;所有多个继承对象都在每次调用基类之前调用一个thunk,这将调整这个指针以指向它内的子对象。在一个简单的例子中:

代码语言:javascript
复制
class ExampleBaseClass
{
    void foo( void );
}

class ExampleDerivedClass : public ExampleBaseClass, private IrrelevantBaseClass
{
    void bar( void );
}

就会变成

代码语言:javascript
复制
void examplebaseclass_foo( ExampleBaseClass* this );
void examplederivedclass_bar( ExampleDerivedClass* this);

void examplederivedclass_thunk_foo( ExampleDerivedClass* this)
{
    examplebaseclass_foo( this + delta );
}

在许多情况下,这可能是内联的,所以它的开销不太大。但是,如果您永远不能将ExampleBaseClass::foo引用为Example导数类::foo,则不需要这些块,因为可以很容易地从调用本身看出增量。

虚拟函数

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

这意味着我们需要对每个具有几乎可重写函数的对象进行某种形式的动态分派;有许多方法是可能的,但是C++实现倾向于使用一个简单的函数指针数组或一个vtable。对于每个具有虚拟函数的对象,我们将一个点作为隐藏成员添加到数组中,通常位于前面:

代码语言:javascript
复制
class A
{
hidden:
    void* vtable;
public:
    virtual void foo( void );
}

我们添加了重定向到vtable的thunk函数。

代码语言:javascript
复制
void a_foo( A* this )
{
    int vindex = 0;
    this->vtable[vindex](this);
}

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

vtable = &A::foo_default;// foo的基本实现

在派生类中,如果我们希望重写这个虚拟函数,我们所需要做的就是更改我们自己的对象中的vtable,以指向新函数,并且它还将在基类中重写:

代码语言:javascript
复制
class B: public A
{
    virtual void foo( void );
}

然后在构造函数中这样做:

代码语言:javascript
复制
((A*)this)->vtable[0] = &B::foo;

最后,我们支持所有形式的继承!差不多了。

虚拟继承这个实现有一个最后的警告:如果您继续允许在您真正指的是Base::foo时使用派生::foo,则会遇到钻石问题:

代码语言:javascript
复制
class A : public B, public C;
class B : public D;
class C : public D;

A::DFunc(); // Which D?

当您使用基类作为有状态类时,或者当您将应该具有-a而不是is-a的函数放在一起时,也会出现这个问题;一般来说,这是需要进行结构调整的一个迹象。但也不总是这样。

在C++中,这有一个不太优雅的解决方案,但效果很好:

代码语言:javascript
复制
class A : public B, public C;
class B : virtual D;
class C : virtual D;

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

我们如何实现这个解决方案?

代码语言:javascript
复制
[ [ D ] [ B ] [ Dptr ] [ C ] [ Dptr ] A ]

与在普通继承中一样,通过虚拟继承,我们不直接使用基类,而是通过指针推送D的所有用法,添加一个间接,同时将多个实例化踩到一个单独的实例中。注意,B和C都有自己的指针,都指向同一个D;这是因为B和C不知道它们是自由浮动副本还是在派生对象中绑定。这两个函数都需要使用相同的调用,否则虚拟函数就不能像预期的那样工作。

摘要

  1. 将方法调用转换为基类中具有特殊参数的函数调用。
  2. 构造内存中的对象,因此单个继承与无继承没有区别。
  3. 添加块以调整此指针,然后调用基类进行多次继承。
  4. 将vtable添加到具有虚拟方法的类中,并对方法进行所有调用(thunk -> vtable ->方法)。
  5. 通过指针到基类对象处理虚拟继承,而不是派生对象调用。

在js.asm中,所有这些都是简单明了的。

票数 2
EN

Stack Overflow用户

发布于 2013-12-29 20:16:08

提姆

我绝不是asm.js专家,但你的问题引起了我的兴趣。它涉及到面向对象语言设计的哈特。同样具有讽刺意味的是,您要在javascript域中重新创建机器级的问题。

在我看来,解决您的问题的方法是,您需要设置定义的类型和函数的记帐。在Java中,这通常是通过用标识符来修饰字节码来完成的,这些标识符表示任何给定对象的正确类->函数映射。如果对已定义的每个类使用Int32标识符,对定义的每个函数使用附加的Int32标识符,则可以将这些标识符存储在堆上的对象表示中。您的vtable不过是将这些组合映射到特定的函数。

希望这能帮到你。

票数 1
EN

Stack Overflow用户

发布于 2013-12-29 23:37:53

我不太熟悉asm.js的确切语法,但以下是我如何在我的编译器(用于x86)中实现vtable:

每个对象都是从如下的结构派生出来的:

代码语言:javascript
复制
struct Object {
    VTable *vtable;
};

然后,我使用的其他类型在c++语法中将类似于以下内容:

代码语言:javascript
复制
struct MyInt : Vtable {
    int value;
};

(在本例中)相当于:

代码语言:javascript
复制
struct MyInt {
    VTable *vtable;
    int value;
};

因此,对象的最终布局是,在偏移量为0时,我知道我有一个指向vtable的指针。我使用的vtable只是一个函数指针数组,同样在C语法中,VTable类型可以定义如下:

代码语言:javascript
复制
typedef Function *VTable;

在C语言中,我将使用void *而不是Function *,因为函数的实际类型会有所不同。编译器所要做的是:

1:对于每个包含虚拟函数的类型,创建一个全局vtable并使用指向重写函数的函数指针填充它。

2:创建对象时,将对象的vtable成员(在偏移量为0)设置为指向全局vtable。

然后,当您想调用虚拟函数时,您可以这样做:

代码语言:javascript
复制
(*myObject->vtable[1])(1);

要调用编译器在vtable中分配的ID 1函数(下面的示例中是methodB)。

最后一个例子:假设我们有以下两个类:

代码语言:javascript
复制
class A {
public:
    virtual int methodA(int) { ... }
    virtual int methodB(int) { ... }
    virtual int methodC(int) { ... }
};

class B : public A {
public:
    virtual int methodA(int) { ... }
    virtual int methodB(int) { ... }
};

A类和B类的VTable可以如下所示:

代码语言:javascript
复制
A:                   B:
0: &A::methodA       0: &B::methodA
1: &A::methodB       1: &B::methodB
2: &A::methodC       2: &A::methodC

通过使用这个逻辑,我们知道当我们对从A派生的任何类型调用methodB时,我们将调用位于该对象vtable中索引1处的任何函数。

当然,如果您希望允许多重继承,此解决方案不会立即工作,但我相当肯定可以扩展此解决方案。在使用Visual 2008进行了一些调试之后,似乎在一定程度上是这样实现vtable的(当然,在那里它被扩展到处理多个继承,我还没有尝试解决这个问题)。

我希望您能得到一些至少可以在asm.js中应用的想法。就像我说的,我不知道asm.js到底是如何工作的,但是我已经设法在x86程序集中实现了这个系统,我也没有看到在JavaScript中实现它的任何问题,所以我希望它也可以在asm.js中使用。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20681758

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档