C++:将 void* 转换为基础 class 指针时发生访问冲突

C++: Access violation when casting void* to base class pointer

给定一个基数classB,例如:

    class B
    {
    public:
        virtual ~B() = default;
    public:
        virtual int f() const = 0;
    };

和一些派生的 classes Ai: public B (i=1,..,N),实现 f(),我收到一个 void* 绝对持有一个来自外部程序的派生 Ai classes - 执行 f() 方法。

可以为每个可能的派生类型创建一个入口点,它会工作得很好:

// for each derived class Ai
void executeF(void* aPtr, int* result)
{
    auto aObjPtr = static_cast<Ai*>(aPtr);
    *result = aObjPtr->f();
}

不过,只用一个函数应该可以达到同样的效果,比如:

void executeF(void* aPtr, int* result)
{
    auto bObjPtr = static_cast<B*>(aPtr); // works
    *result = bObjPtr->f(); // Access violation
}

上面的案例成功了,但是f()在MSVC 2013中执行失败"Access violation"。

是不是上面的函数有问题?如果是这样,有没有办法用一个函数来完成任务?

我读过一些资料,其中声称必须将 void* 仅投射到它持有的特定 class(也在下面的评论中建议)。但是,这段代码可以正常编译和执行:http://ideone.com/e0Lr6v

关于如何调用所有内容的更多上下文:

我不能在这里提供完整的代码,因为它太长了,但总而言之。函数 executeF、对象的构造函数 Ai 以及库中定义和操作对象的所有内容AB 作为仅在 void* 类型上运行的导出函数提供。仅供参考,此库正在使用 MSVC 2013 进行编译和构建。

另一端(R 语言的包装器)是用 g++ 编译和构建的 - 它动态加载上述库,导出所需的函数并调用它。这边唯一可用的是 void* 持有对象 Ai - 它只是发送创建对象的请求,调用它们的方法,释放它们。

例如(示意图),创建一个类型为 A1 的对象:

// "objects" library
void createA1(void** aObj)
{
    *a1Obj = new A1();
}

// caller library
auto const createA1func = (int(__CC *)(void**)) GetProcAddress(getDLL(), "CreateA1");
void* a1Obj = NULL;
createAFunc(a1Obj);
// ... return the a1Obj to the external environemnt to keep it around 

然后,有了 a1Obj,用它做一些工作:

// caller library
auto const executeFfunc = (int(__CC *)(void*, int*)) GetProcAddress(getDLL(), "executeF");
int res(0);
executeFfunc(a1Obj, &res);

所以如果我在两边为每个类型Ai写一个单独的函数,一切正常。但是,如果我可以在这里以某种方式使用基础 class,那么样板代码就会少得多。

The function executeF, constructors for objects Ai...

这很可能是问题所在,您不应在构造函数中调用虚拟。它适用于 Ai,因为 Ai 没有从 vptr table 调用虚拟方法。 B 然而还没有这样的 table 如果它正在构建。请参阅 this other SO 答案。

AiB 派生时,指向对象 Ai 部分的指针(通常)与指向 B 同一对象的一部分(特别是如果 B 中有数据字段)。通过 B* 指针访问 Ai 通常涉及指针修复、VMT 查找等,使用的特定编译器必须考虑这些事情。这就是为什么您不能简单地将指向 void*Ai* 指针转换为 B* 并期望一切正常工作的原因。 B* 指针不是有效的 B* 指针,它实际上是一个 Ai* 指针,已被 重新解释B*,这根本不合法。

为确保物品正确排列,您必须:

  • Ai* 转换为 void*,然后将 void* 转换为 Ai*。这正是您要避免的。

  • 首先将Ai*转换为B*,然后将B*转换为void*,然后将void*转换为B* ](如果您需要访问 Ai 的非虚拟成员,则可以选择通过 dynamic_cast B*Ai*)。

因此,为了使其按照您想要的方式工作,请在构建对象时执行以下操作:

void createA1(void** aObj)
{
    *aObj = static_cast<B*>(new A1());
}

void createA2(void** aObj)
{
    *aObj = static_cast<B*>(new A2());
}

等等。这样可以确保所有传递给executeF()的指针都是正确的B*指针,只有这样才能executeF()安全 将其收到的 void* 指针类型转换为 B* 并使用多态性访问它实际指向的派生 class:

void executeF(void* aPtr, int* result)
{
    B* bObjPtr = static_cast<B*>(aPtr);
    *result = bObjPtr->f(); // works
}

Update:或者,尤其是在处理多个派生的 classes 时,每个派生的 classes 都有多个基础 classes,这些 classes 可能共享也可能不共享, 另一种选择是简单地将 Ai 对象包装在 struct 中,该 struct 有一个额外的字段来指示对象类型。然后你的 create...() 函数可以 return void* 指向该结构的指针而不是直接指向 Ai 对象,并且 execute...() 函数可以首先转换 void* 到该结构,查看其类型字段,并相应地转换对象指针:

enum AType
{
    a1, a2 /*, ... */
};

class B
{
public:
    virtual ~B() = default;
    virtual int f() = 0;
};

class Bx
{
public:
    virtual ~B() = default;
    virtual int x() = 0;
};

class By
{
public:
    virtual ~B() = default;
    virtual int y() = 0;
};

// ...

class A1 : public B, public Bx
{
public:
    int f() override { return 1; }
    int x() override { return 1; }
};

class A2 : public B, public By
{
public:
    int f() override { return 2; }
    int y() override { return 2; }
};

// ...

struct objDesc
{
    AType type;
    void *obj;
};

void createA1(void** aObj)
{
    objDesc *desc = new objDesc;
    desc->type = a1;
    desc->obj = new A1();
    *aObj = desc;
}

void createA2(void** aObj)
{
    objDesc *desc = new objDesc;
    desc->type = a2;
    desc->obj = new A2();
    *aObj = desc;
}

// ...

void destroyObj(void* aObj)
{
    objDesc *desc = static_cast<objDesc*>(aObj);
    switch (desc->type)
    {
        case a1:
            delete static_cast<A1*>(desc->obj);
            break;

        case a2:
            delete static_cast<A2*>(desc->obj);
            break;

        //..
    }

    delete desc;
}

//...

void executeF(void* aPtr, int* result)
{
    objDesc *desc = static_cast<objDesc*>(aPtr);
    B* bObjPtr = nullptr;

    switch (desc->type)
    {
        case a1:
            bObjPtr = static_cast<A1*>(desc->obj);
            break;

        case a2:
            bObjPtr = static_cast<A2*>(desc->obj);
            break;

        // other classes that implement B ...
    }

    if (bObjPtr)
        *result = bObjPtr->f();
}

void executeX(void* aPtr, int* result)
{
    objDesc *desc = static_cast<objDesc*>(aPtr);
    Bx* bObjPtr = nullptr;

    switch (desc->type)
    {
        case a1:
            bObjPtr = static_cast<A1*>(desc->obj);
            break;

        // other classes that implement Bx ...
    }

    if (bObjPtr)
        *result = bObjPtr->x();
}

void executeY(void* aPtr, int* result)
{
    objDesc *desc = static_cast<objDesc*>(aPtr);
    By* bObjPtr = nullptr;

    switch (desc->type)
    {
        case a2:
            bObjPtr = static_cast<A2*>(desc->obj);
            break;

        // other classes that implement By ...
    }

    if (bObjPtr)
        *result = bObjPtr->y();
}

// ...

它不是理想的或不灵活的,但它会在你对另一方的限制内工作。

否则,您可以用 all other classes 必须的新碱基 class 替换 struct 派生自,那么你可以根据需要使用dynamic_cast

class Base
{
public:
    virtual ~Base() = default;
};

class Bf
{
public:
    virtual ~Bf() = default;
    virtual int f() = 0;
};

class Bx
{
public:
    virtual ~Bx() = default;
    virtual int x() = 0;
};

class By
{
public:
    virtual ~By() = default;
    virtual int y() = 0;
};

class Bz
{
public:
    virtual ~Bz() = default;
    virtual int z() = 0;
};

class A1 : public Base, public Bf, public Bx
{
public:
    int f() override { return 1; }
    int x() override { return 1; }
};

class A2 : public Base, public Bf, public By
{
public:
    int f() override { return 2; }
    int y() override { return 2; }
};

class A3 : public Base, public Bz
{
public:
    int z() override { return 3; }
};

// ...

void createA1(void** aObj)
{
    *aObj = static_cast<Base*>(new A1());
}

void createA2(void** aObj)
{
    *aObj = static_cast<Base*>(new A2());
}

void createA3(void** aObj)
{
    *aObj = static_cast<Base*>(new A3());
}

// ...

void destroyObj(void* aObj)
{
    delete static_cast<Base*>(aObj);
}

//...

void executeF(void* aPtr, int* result)
{
    Base *base = static_cast<Base*>(aPtr);
    B* bObjPtr = dynamic_cast<B*>(base);
    if (bObjPtr)
        *result = bObjPtr->f();
}

void executeX(void* aPtr, int* result)
{
    Base *base = static_cast<Base*>(aPtr);
    Bx* bObjPtr = dynamic_cast<Bx*>(base);
    if (bObjPtr)
        *result = bObjPtr->x();
}

void executeY(void* aPtr, int* result)
{
    Base *base = static_cast<Base*>(aPtr);
    By* bObjPtr = dynamic_cast<By*>(base);
    if (bObjPtr)
        *result = bObjPtr->y();
}

void executeZ(void* aPtr, int* result)
{
    Base *base = static_cast<Base*>(aPtr);
    By* bObjPtr = dynamic_cast<Bz*>(base);
    if (bObjPtr)
        *result = bObjPtr->z();
}

//...

您观察到的行为只是意味着从 Ai *B * 的转换不是纯粹概念上的,而是实际上需要指针值的物理变化。在典型的实现中,这通常发生在:

  1. Class B 不是多态的,包含非零大小的子对象,而 class Ai 是多态的。 (不是你的情况)
  2. Class Ai 有多个碱基,B 只是其中之一。

我猜你正在处理代码中的第二种情况。

在这种情况下,如果您确保基数 BAi第一个 基数(但是,同样,这在很大程度上取决于实现,而且显然不可靠)。

我提出了以下可行的解决方案,它避免了两边都有 2*N 个函数,其中 N 是派生的 A classes 的数量。相反,它涉及 2 个功能,每侧一个。对象库有一个带有 N 个案例的开关,它将 void* 转换为适当的 class。请注意,g++ 端确实只需要知道 enum 并且仍然对类型一无所知。

不确定这是否是完美的方法,但看起来非常简洁和安全。对其他 solutions/comments.

仍有兴趣

http://ideone.com/enNl3f

enum AType
{
    a1 = 1, a2
};

class B
{
public:
    virtual ~B() = default;
public:
    virtual int f() const = 0;
};

class A1: public B
{
    virtual int f() const override
    {
        return 1;
    }
};
class A2: public B
{
    virtual int f() const override
    {
        return 2;
    }
};

void executeF(void* aPtr, AType aType, int* result)
{
    B* bPtr = nullptr;
    switch(aType)
    {
        case a1:
            bPtr = static_cast<A1*>(aPtr);
            break;
        case a2:
            bPtr = static_cast<A2*>(aPtr);
            break;
        default:
            break;
    }

    if(bPtr)
        *result = bPtr->f();
}

想象一下,如果有两种类型,Base1Base2。假设 Base1 只包含一个成员,一个整数。并说 Base2 只包含一个成员,一个浮点数。

人们会期望 Base1* 将指向整数,而 Base2* 将指向浮点数。

现在,考虑:

class Derived : public Base1, public Base2
{
    ...

现在,如果我们将 Derived* 转换为 void*,我们可以获得指向 Base1 中整数的指针,或者我们可以获得指向Base2但我们不可能两者兼得。

因此,期望您可以将 Derived* 转换为 void*,然后将其转换回指向基 class 的指针并得到一些合理的东西,这是不可能的.将指向基数 class 的指针转换为指向派生自 的 class 的指针,有时必须 更改该指针的值。