如何检查 void* 是否指向对象类型的有效实例上的指针

How to check if a void* is to a pointer on a valid instance of an object type?

本文关键字:类型 对象 有效 指针 实例 是否 何检查 检查 void      更新时间:2023-10-16

我正在寻找最常见和最强大的方法来检查是否可以在给定的C++对象类型中转换void*。您可以在下面看到有关上下文的一些信息。

当我为 DLL 定义 C API 时,我经常使用 void* 来隐藏我在后面使用C++对象(如下所示(

typedef void* Instance_t;
int createInstance(Instance_t* pInst);
int processing(Instance_t inst, uint8_t* pBuf, size_t bufLength);

当我createInstance代码时,cast这样的指针:

int createInstance(Instance_t* pInst)
{ 
   MyClass* ptr = new MyClass();
   *pInst = (void*)(ptr);
   //.... etc
   return 0;
 }

但问题是我们如何稍后在我们定义的所有其他 C 函数中检查我们收到的void*值是否有效MyClass* .我认为我们不能,因为在这种情况下(即使是dynamic_cast(,C++铸造运算符中没有一个是真正类型安全的。

目前,我最好的解决方案是使用 C 强制转换(或 reinterpret_cast(,如果调用 IsValid 函数定义一切正常,请使用 MyClass 定义。

你有更好的方法来做那个检查吗?

你不能

这样做,除非你(比如(从内存池中分配所有MyClass实例,并检查你传递的地址是指向该内存池的指针。或者维护有效实例的列表。

但是,如果您需要传递不透明的指针,只需让客户端使用

struct MyClass;
typedef struct MyClass *Instance_t;

这将干净地编译并让您高枕无忧。只要你只使用指针,编译器就很高兴。当它取消引用它时,编译器需要知道指针实际指向什么。

我不

认为你可以这样做,我认为你不应该这样做。void *只是指向内存中某个位置的指针。几乎根据定义,没有办法知道它指向什么。

但是为什么要将所有内容键入void *,如果您想防止用户摆弄类的内部结构,为什么不在类上使用受保护和私有方法呢?

无法检查非类型化指针是否指向任何特定类型的有效对象。如果使用 void* ,则会丢弃类型检查。相反,您可以在 C 标头中声明类型,并使用指向该(不完整(类型的指针,而不是 void*

struct Instance;
int createInstance(struct Instance** pInst);
int processing(struct Instance* inst, uint8_t* pBuf, size_t bufLength);

然后在C++中,您可以定义和使用类。

// Probably better to use "struct" rather than "class" in case
// some compilers complain about mixing class keys.
struct Instance {
    // Whatever
};
int createInstance(Instance** pInst) {
    *pInst = new Instance;
    // and so on
}

无法确定void*是否指向有效的C++类。 除非您启用了 RTTI,否则没有与类关联的元数据,即便如此,C++中也有很多情况下void*不指向类。例如:

int x=10;
void *ptr = &x;

这里ptr指向一个原始值。没有与整数关联的 RTTI,因此您如何查询它以确定任何内容><</p>

div class="answers>

当我需要将一些对象从我的 CPP 库导出到 C 代码时,我会这样做:

typedef void * OBJ1;
typedef void * OBJ2;
OBJ1 createObj1();
OBJ2 createObj2();
void doObj1(OBJ1 obj);

所以在 do 函数中,我完全知道要期待什么对象

简短的回答:这可能很困难。

这些问题很多,但基本上归结为在 C 和 C++ 中可访问的操作的非常低级性质(注意:可访问,但不一定合法(。void*的作用是任何指针都可以被强制指向它,但是如果你无论如何使用另一种类型,滥用reinterpret_cast仍然可能导致麻烦。


一个简单的解决方案是使用标记。实质上,在指针中放置一个类型 id,以便您始终可以知道对象的原始类型。它很繁重(因为每种类型都需要修改(,但在其他方面易于部署。

typedef enum {
    SP_ATag,
    SP_BTag,
    SP_CTag,
    ...
} SP_Tag_t;
// External Tag
typedef struct {
    SP_Tag_t tag;
    void* p;
} SP_Any_t;
// Internal Tag
struct A {
    SP_Tag_t tag;
    ...;
};
...
typedef union {
    A* a;
    B* b;
    C* c;
} SP_Any_t;

然后,不使用void*,而是使用SP_Any_t

优点

  • 外部解决方案不需要修改现有类
  • 内部解决方案应与现有 C 代码二进制兼容

缺点

  • 单一位置声明所有类型
  • 损坏标签很容易(无论是意外还是故意(

一个更复杂的解决方案(可能是一个很好的调试帮助(是引入每类型注册表。缺点是您需要检测现有类型才能工作,但它仍然很容易,并且它涉及更多的运行时开销。但是,嘿:它有效!

template <typename> class Registrable;
//
// class Registry
//
class Registry {
public:
template <typename> friend class Registrable;
template <typename T>
static T* Cast(void*);
private:
struct TagType {};
using Key = std::pair<TagType const*, void*>;
using Store = std::set<Key>;
template <typename T>
static void Register(Registrable<T>* t);
template <typename T>
static void Unregister(Registrable<T>* t);
static Store& Get();
}; // class Registry
template <typename T>
T* Registry::Cast(void* const pointer) {
TagType const* const tag = &Registrable<T>::Tag;
if (Get().count(std::make_pair(tag, pointer)) == 0) { return nullptr; }
return static_cast<T*>(reinterpret_cast<Registrable<T>*>(pointer));
}
template <typename T>
void Registry::Register(Registrable<T>* t) {
TagType const* const tag = &T::Tag;
void* const pointer = reinterpret_cast<void*>(t);
Get().insert(std::make_pair(tag, pointer));
}
template <typename T>
void Registry::Unregister(Registrable<T>* t) {
TagType const* const tag = &T::Tag;
void* const pointer = reinterpret_cast<void*>(t);
Get().erase(std::make_pair(tag, pointer));
}
Registry::Store& Registry::Get() { static Store S; return S; }
//
// class Registrable
//
template <typename T>
class Registrable {
public:
static Registry::TagType const Tag;
Registrable();
~Registrable();
Registrable(Registrable&&) = default;
Registrable& operator=(Registrable&&) = default;
Registrable(Registrable const&) = default;
Registrable& operator=(Registrable const&) = default;
}; // class Registrable
template <typename T> Registry::TagType const Registrable<T>::Tag;
template <typename T>
Registrable<T>::Registrable() { Registry::Register(this); }
template <typename T>
Registrable<T>::~Registrable() { try { Registry::Register(this); } catch(...) {} }
不使用

指针而是使用句柄

  • 如果内存在 DLL 端,请不要传递指针,而是将句柄传递给仅限您的对象
  • 如果对象的内存由应用程序分配并传递给 DLL,请保留这些指针的表,并将它们也视为句柄

这适用于单个 DLL。 当然,如果从 1.dll 到.dll的指针通过应用程序传递到 2,则不起作用。 在这种情况下,无论如何,您都在最薄的冰上。

我有一个使用 RTTI 的解决方案,有一些限制......

如果您的实例都派生自虚拟基类,那么您可以安全地将强制转换重新解释为该基类,然后将动态强制转换为其他类...

class Object
{
   virtual ~Object() {}
};
class A : public Object
{
  static bool IsOfThisClass(void *data)
  { 
     return dynamic_cast<A*>((Object*)data) != 0;
  }
}

调用 A::IsOfThisClass(someData( 将返回 true,如果 someData 属于类 A。

这不是你想要向用户公开的那种黑客,因为它只有在 void* 指向从 Object 派生的类时才有效,但它在受控情况下可以是一个有用的构建块。