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
以及库中定义和操作对象的所有内容A
、B
作为仅在 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 答案。
当 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*
](如果您需要访问 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 *
的转换不是纯粹概念上的,而是实际上需要指针值的物理变化。在典型的实现中,这通常发生在:
- Class
B
不是多态的,包含非零大小的子对象,而 class Ai
是多态的。 (不是你的情况)
- Class
Ai
有多个碱基,B
只是其中之一。
我猜你正在处理代码中的第二种情况。
在这种情况下,如果您确保基数 B
是 Ai
的 第一个 基数(但是,同样,这在很大程度上取决于实现,而且显然不可靠)。
我提出了以下可行的解决方案,它避免了两边都有 2*N 个函数,其中 N 是派生的 A
classes 的数量。相反,它涉及 2 个功能,每侧一个。对象库有一个带有 N 个案例的开关,它将 void*
转换为适当的 class。请注意,g++ 端确实只需要知道 enum
并且仍然对类型一无所知。
不确定这是否是完美的方法,但看起来非常简洁和安全。对其他 solutions/comments.
仍有兴趣
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();
}
想象一下,如果有两种类型,Base1
和 Base2
。假设 Base1
只包含一个成员,一个整数。并说 Base2
只包含一个成员,一个浮点数。
人们会期望 Base1*
将指向整数,而 Base2*
将指向浮点数。
现在,考虑:
class Derived : public Base1, public Base2
{
...
现在,如果我们将 Derived*
转换为 void*
,我们可以获得指向 Base1
中整数的指针,或者我们可以获得指向Base2
。 但我们不可能两者兼得。
因此,期望您可以将 Derived*
转换为 void*
,然后将其转换回指向基 class 的指针并得到一些合理的东西,这是不可能的.将指向基数 class 的指针转换为指向派生自 的 class 的指针,有时必须 更改该指针的值。
给定一个基数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
以及库中定义和操作对象的所有内容A
、B
作为仅在 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 答案。
当 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*
](如果您需要访问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 *
的转换不是纯粹概念上的,而是实际上需要指针值的物理变化。在典型的实现中,这通常发生在:
- Class
B
不是多态的,包含非零大小的子对象,而 classAi
是多态的。 (不是你的情况) - Class
Ai
有多个碱基,B
只是其中之一。
我猜你正在处理代码中的第二种情况。
在这种情况下,如果您确保基数 B
是 Ai
的 第一个 基数(但是,同样,这在很大程度上取决于实现,而且显然不可靠)。
我提出了以下可行的解决方案,它避免了两边都有 2*N 个函数,其中 N 是派生的 A
classes 的数量。相反,它涉及 2 个功能,每侧一个。对象库有一个带有 N 个案例的开关,它将 void*
转换为适当的 class。请注意,g++ 端确实只需要知道 enum
并且仍然对类型一无所知。
不确定这是否是完美的方法,但看起来非常简洁和安全。对其他 solutions/comments.
仍有兴趣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();
}
想象一下,如果有两种类型,Base1
和 Base2
。假设 Base1
只包含一个成员,一个整数。并说 Base2
只包含一个成员,一个浮点数。
人们会期望 Base1*
将指向整数,而 Base2*
将指向浮点数。
现在,考虑:
class Derived : public Base1, public Base2
{
...
现在,如果我们将 Derived*
转换为 void*
,我们可以获得指向 Base1
中整数的指针,或者我们可以获得指向Base2
。 但我们不可能两者兼得。
因此,期望您可以将 Derived*
转换为 void*
,然后将其转换回指向基 class 的指针并得到一些合理的东西,这是不可能的.将指向基数 class 的指针转换为指向派生自 的 class 的指针,有时必须 更改该指针的值。