C ++:将void *转换为基类指针时访问冲突

Posted

技术标签:

【中文标题】C ++:将void *转换为基类指针时访问冲突【英文标题】:C++: Access violation when casting void* to base class pointer 【发布时间】:2017-02-22 02:09:04 【问题描述】:

给定一个基类B,如:

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

和许多派生类Ai: public B (i=1,..,N),实现f(),我收到一个void*,肯定持有来自外部程序的派生Ai 类之一 - 到执行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 出现“访问冲突”。

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

我已经阅读了一些材料,其中声称必须将 void* 仅投射到它所拥有的特定类(也在下面的评论中建议)。但是,这段代码编译并执行得很好:http://ideone.com/e0Lr6v

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

我无法在此处提供整个代码,因为它太长了,但总而言之.. 函数 executeF、对象构造函数 Ai 以及库中定义和操作对象 A、@987654336 的所有内容@ 作为仅在 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 的每种类型编写一个单独的函数,一切正常。但如果我能以某种方式在这里使用基类,那么样板代码就会大大减少。

【问题讨论】:

你可以在调用executeF之前尝试强制转换,这样executeF总是得到B*而不是Ai* 当从void * 转换时,只有当您转换回与转换来源相同的类型时,才能保证工作。不是它的基类或派生类。 请不要同时标记C和C++;两个标签都有用的情况很少见。在这种情况下,您使用的是 C++ 编译器,而 C 编译器会发出不属于您的问题的错误消息……因此您的代码是 C++,这就是您应该使用的标签。 如果将A1*指针作为void*传递给DLL,则不能将executeF()中的void*直接转换为B*,必须将其转换为A1* .如果您将多个A1*A2*A3* 等指针传递给DLL,请先将它们全部转换为B*,然后再将它们作为void* 传递给DLL,然后您可以将void* 转换为@ 987654361@直接到B* @OlegShirokikh 对象指针如何准确进入 DLL? DLL 如何确切地知道使用对象指针调用executeF()?您遗漏了重要信息。 一些 代码正在创建对象并将它们转换为void*那个代码需要先将它们转换为B*,然后再将B*转换为void* 【参考方案1】:

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

为确保事情保持正确排列,您必须:

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

首先将Ai* 转换为B*,然后将B* 转换为void*,然后将void* 转换为B*(然后可选地将B* 转换为Ai* 通过@987654347如果您需要访问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* 指针类型转换为@ 987654354@ 并使用多态性来访问它实际指向的派生类:

void executeF(void* aPtr, int* result)

    B* bObjPtr = static_cast<B*>(aPtr);
    *result = bObjPtr->f(); // works


更新:或者,特别是在处理多个派生类时,每个派生类都有多个可能共享或不共享的基类,另一种选择是将Ai 对象简单地包装在一个struct 有一个额外的字段来指示对象类型。然后您的create...() 函数可以返回指向该结构的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();


// ...

它并不理想或不灵活,但它会在你对另一边的限制范围内工作。

否则,您可以将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();


//...

【讨论】:

这可能是最简单的解决方法,但为什么要使用 C 样式转换? 我把它改成了 C++ casts。 谢谢。好吧,这会起作用,是的,但它会削减所有 Ai 特定功能。除了B 之外,还有一堆基类......无论如何,我认为我必须为每个Ai 类型做这件事 @OlegShirokikh “除了 B 之外,还有很多基类。”当需要非零修正时,情况正是如此。它几乎可以保证不起作用。 @OlegShirokikh "它将削减所有 Ai 特定功能" - 不,它不会。您仍然在内存中有完整的Ai 对象,您只是在传递指向它们的B 部分的指针。剩下的就是多态性。如果您需要访问非虚拟的Ai 功能,请使用dynamic_castB* 转换为Ai*。 “除了 B 之外,还有一堆基类”——好吧,那么你的整个解决方案就会消失。如果你想为多个类使用一个函数,它们必须有一个共同的基类,并且你必须传递指向该基类的指针。【参考方案2】:

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

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

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

在这种情况下,如果您确保基础 BAi第一个基础,它可能会“工作”(但是,这又是严重依赖于实现的,并且显然,不可靠)。

【讨论】:

是的,有(可能)不止一个基类【参考方案3】:

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

不确定这是否是完美的方法,但看起来非常简洁和安全。仍然对其他解决方案/cmets 感兴趣。

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();

【讨论】:

虽然这可行,但并不理想。至少,我会去掉executeF() 上的aType 参数,并将Ai 对象包装在struct 中,该struct 有一个AType 字段来指定对象类型。然后让您的 create...() 函数返回指向该结构的指针,而不是指向 Ai 对象本身的指针。这将允许execute...() 函数查看 AType 结构字段并进行相应的转换。我会用一个例子来更新我的答案。 我可以看到额外的父结构如何替换enum,但这将涉及对现有代码的重大重写:1. 从这个新的基础结构继承每个 A 类 2. 修改每个ctor 向上转换创建的 A 对象而使用 enum 时,现有代码保持不变。这比enum 有什么好处 我建议的不是类必须直接派生的新基类型。它只是一个包装器,它允许create...() 函数将额外的enum 传递给execute...() 函数,而根本不改变现有的类。查看我的答案的更新。 好的,明白了。那么为什么这有利于enum 方法呢? 它是一个使用枚举的包装器,而不是避免它。目的是让void* 指针指向带有enum 的东西,因此调用者不必担心跟踪void* 指针指向的类型,因此它可以将该信息传递给函数手动。这让void* 指针代表调用者透明地处理它。【参考方案4】:

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

人们会认为Base1* 将指向整数,Base2* 将指向浮点数。

现在,考虑:

class Derived : public Base1, public Base2

    ...

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

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

【讨论】:

【参考方案5】:

函数executeF,对象Ai的构造函数...

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

【讨论】:

以上是关于C ++:将void *转换为基类指针时访问冲突的主要内容,如果未能解决你的问题,请参考以下文章

C指针转换导致内存访问冲突

c ++:将指针传递给基类时访问派生类方法?

在c#中将基类动态转换为子类

?dynamic_caast操作符

static_casting 中等数量的 T* 到 void* 时访问冲突

有没有办法在不知道派生类型的情况下将包含派生指针的 std::any 转换为基指针?