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

时间:2022-08-25 17:04:59

I'm searching for the most common and robust way to check if a void* can be convert in a given C++ object type. You can see below some information about the context.

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

When I define a C API for a DLL, I often use void* to hide the C++ object I use behind (something like below)

当我为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);

When I createInstance the code cast a pointer like that:

当我在createInstance中时,代码会抛出一个像这样的指针:

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

But the question is how can we later in all the other C function we define, check if the void* value we receive is a valid MyClass*. I think we can't as none of the C++ casting operator is really type safe is that case (even dynamic_cast).

但问题是我们如何在后面的所有其他C函数中定义,检查我们收到的void *值是否是有效的MyClass *。我认为我们不能,因为C ++转换操作符都不是真正的类型安全(即使是dynamic_cast)。

for now my best solution is to use a C cast (or a reinterpret_cast) and if everything is ok with a call to a IsValid function define with MyClass.

现在我最好的解决方案是使用C cast(或reinterpret_cast),如果一切正常,调用IsValid函数定义MyClass。

Have you a better way to do that check ?

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

8 个解决方案

#1


7  

You can't do that, unless you (say) allocate all your MyClass instances from a memory pool, and check the address you get passed is a pointer into to that memory pool. Or maintain a list of valid instances.

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

However, if you need to pass round an opaque pointer just make the client use

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

struct MyClass;
typedef struct MyClass *Instance_t;

This will compile cleanly and give you a reasonable amount of peace of mind. As long as you're only using the pointer, the compiler is happy. It's when it dereferences it, the compiler needs to know what the pointer actually points to.

这将清晰地编译并给您一定的安心。只要您只使用指针,编译器就会很高兴。它解引它时,编译器需要知道指针实际指向的是什么。

#2


5  

I don't think you can do this, and I don't think you should be doing this. A void * is just a pointer to some location in memory. Almost by definition, there's no way to know what it points to.

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

But why are you typecasting everything to void *'s, why not use protected and private methods on your class if you want to prevent users from fiddling with the internals of your class?

但是,为什么你要将所有内容转换为void *,为什么不在你的类上使用受保护和私有方法,如果你想阻止用户摆弄你的类的内部?

#3


3  

There is no way to check that an untyped pointer points to a valid object of any particular type. If you use void*, you are throwing away type checking. Instead, you could declare the type in the C header, and use a pointer to that (incomplete) type rather than void*.

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

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

Then in C++, you can define and use the class.

然后在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
}

#4


1  

There's no way to determine if a void* points to a valid C++ class. Unless you've got RTTI enabled then there's no metadata associated with a class, and even then there are plenty of cases in C++ where a void* isn't pointing to a class. For example:

无法确定void *是否指向有效的C ++类。除非你已经启用了RTTI,否则没有与类关联的元数据,即使这样,C ++中也有很多情况,其中void *没有指向类。例如:

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

Here ptr is pointing to a raw value. There's no RTTI associated with the integer so how could you query it to determine anything>

这里ptr指向原始值。没有RTTI与整数关联,所以你如何查询它来确定任何>

#5


0  

When I need to export some objects from my CPP lib to C code, I do:

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

typedef void * OBJ1;
typedef void * OBJ2;

OBJ1 createObj1();
OBJ2 createObj2();

void doObj1(OBJ1 obj);

So in do function I exactly knew what object to expect

所以在do函数中我确切地知道了什么对象

#6


0  

The short answer: it can be difficult.

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

The issues are numerous, but basically boil down to the very low-level nature of operations accessible in C and C++ (note: accessible, but not necessarily legal). The very role of void* is that any pointer can be coerced to it, but should you be using another type anyway abuse of reinterpret_cast could still lead to troubles.

问题很多,但基本上归结为C和C ++中可访问的操作的低级性质(注意:可访问,但不一定合法)。 void *的作用是任何指针都可以被强制转换,但是如果你使用其他类型,滥用reinterpret_cast仍然会导致麻烦。


A simple solution is to use tagging. Essentially, put a type id in the pointer so that you can always know the original type of the object. It's onerous (as each and every type need be modified), but otherwise easy to deploy.

一个简单的解决方案是使用标记。本质上,在指针中放置一个类型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;

Then, instead of using void*, you use SP_Any_t.

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

Advantages:

  • lightweight
  • external solution does not require modifying existing classes
  • 外部解决方案不需要修改现有类

  • internal solution should be binary compatible with existing C code
  • 内部解决方案应与现有C代码二进制兼容

Disadvantages:

  • single place to declare all types
  • 单一地方申报所有类型

  • corrupting the tag is easy (whether accidental or intentional)
  • 破坏标签很容易(无论是偶然的还是故意的)


A more involved solution, which can be a good debug help, is to introduce a per type registry. The downside is that you need to instrument existing types for it to work, still it's easy enough, and that it involves much more runtime overhead. But hey: it works!

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

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(...) {} }

#7


0  

do not use pointers but handles

不要使用指针而是句柄

  • if the memory is on the dll side do not pass pointers but handles to your objects only
  • 如果内存在dll端,则不传递指针,只处理对象

  • if the memory for your objects is allocated by the application and passed to the dll keep a table of those pointers and treat them as handles also
  • 如果您的对象的内存由应用程序分配并传递给dll,请保留这些指针的表并将它们视为句柄

this works for a single dll. of course it does not work if pointers from 1.dll are passed to 2.dll via application. in this case you are on thinnest possible ice anyway.

这适用于单个dll。当然,如果来自1.dll的指针通过应用程序传递给2.dll,它就不起作用。在这种情况下,无论如何你都在尽可能冰上。

#8


0  

I have a solution with some limitations that uses RTTI...

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

if your instances all derive from a virtual base class, then you can reinterpret cast to that base class safely, and then dynamic cast to your other class...

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

class Object
{
   virtual ~Object() {}
};

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

calling A::IsOfThisClass(someData) will return true if someData is of class A.

如果someData属于A类,则调用A :: IsOfThisClass(s​​omeData)将返回true。

it's not the kind of hack you want to expose to a user, since it only works if void* points to a class derived from Object, but it can be a useful building block in controlled situations.

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

#1


7  

You can't do that, unless you (say) allocate all your MyClass instances from a memory pool, and check the address you get passed is a pointer into to that memory pool. Or maintain a list of valid instances.

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

However, if you need to pass round an opaque pointer just make the client use

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

struct MyClass;
typedef struct MyClass *Instance_t;

This will compile cleanly and give you a reasonable amount of peace of mind. As long as you're only using the pointer, the compiler is happy. It's when it dereferences it, the compiler needs to know what the pointer actually points to.

这将清晰地编译并给您一定的安心。只要您只使用指针,编译器就会很高兴。它解引它时,编译器需要知道指针实际指向的是什么。

#2


5  

I don't think you can do this, and I don't think you should be doing this. A void * is just a pointer to some location in memory. Almost by definition, there's no way to know what it points to.

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

But why are you typecasting everything to void *'s, why not use protected and private methods on your class if you want to prevent users from fiddling with the internals of your class?

但是,为什么你要将所有内容转换为void *,为什么不在你的类上使用受保护和私有方法,如果你想阻止用户摆弄你的类的内部?

#3


3  

There is no way to check that an untyped pointer points to a valid object of any particular type. If you use void*, you are throwing away type checking. Instead, you could declare the type in the C header, and use a pointer to that (incomplete) type rather than void*.

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

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

Then in C++, you can define and use the class.

然后在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
}

#4


1  

There's no way to determine if a void* points to a valid C++ class. Unless you've got RTTI enabled then there's no metadata associated with a class, and even then there are plenty of cases in C++ where a void* isn't pointing to a class. For example:

无法确定void *是否指向有效的C ++类。除非你已经启用了RTTI,否则没有与类关联的元数据,即使这样,C ++中也有很多情况,其中void *没有指向类。例如:

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

Here ptr is pointing to a raw value. There's no RTTI associated with the integer so how could you query it to determine anything>

这里ptr指向原始值。没有RTTI与整数关联,所以你如何查询它来确定任何>

#5


0  

When I need to export some objects from my CPP lib to C code, I do:

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

typedef void * OBJ1;
typedef void * OBJ2;

OBJ1 createObj1();
OBJ2 createObj2();

void doObj1(OBJ1 obj);

So in do function I exactly knew what object to expect

所以在do函数中我确切地知道了什么对象

#6


0  

The short answer: it can be difficult.

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

The issues are numerous, but basically boil down to the very low-level nature of operations accessible in C and C++ (note: accessible, but not necessarily legal). The very role of void* is that any pointer can be coerced to it, but should you be using another type anyway abuse of reinterpret_cast could still lead to troubles.

问题很多,但基本上归结为C和C ++中可访问的操作的低级性质(注意:可访问,但不一定合法)。 void *的作用是任何指针都可以被强制转换,但是如果你使用其他类型,滥用reinterpret_cast仍然会导致麻烦。


A simple solution is to use tagging. Essentially, put a type id in the pointer so that you can always know the original type of the object. It's onerous (as each and every type need be modified), but otherwise easy to deploy.

一个简单的解决方案是使用标记。本质上,在指针中放置一个类型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;

Then, instead of using void*, you use SP_Any_t.

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

Advantages:

  • lightweight
  • external solution does not require modifying existing classes
  • 外部解决方案不需要修改现有类

  • internal solution should be binary compatible with existing C code
  • 内部解决方案应与现有C代码二进制兼容

Disadvantages:

  • single place to declare all types
  • 单一地方申报所有类型

  • corrupting the tag is easy (whether accidental or intentional)
  • 破坏标签很容易(无论是偶然的还是故意的)


A more involved solution, which can be a good debug help, is to introduce a per type registry. The downside is that you need to instrument existing types for it to work, still it's easy enough, and that it involves much more runtime overhead. But hey: it works!

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

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(...) {} }

#7


0  

do not use pointers but handles

不要使用指针而是句柄

  • if the memory is on the dll side do not pass pointers but handles to your objects only
  • 如果内存在dll端,则不传递指针,只处理对象

  • if the memory for your objects is allocated by the application and passed to the dll keep a table of those pointers and treat them as handles also
  • 如果您的对象的内存由应用程序分配并传递给dll,请保留这些指针的表并将它们视为句柄

this works for a single dll. of course it does not work if pointers from 1.dll are passed to 2.dll via application. in this case you are on thinnest possible ice anyway.

这适用于单个dll。当然,如果来自1.dll的指针通过应用程序传递给2.dll,它就不起作用。在这种情况下,无论如何你都在尽可能冰上。

#8


0  

I have a solution with some limitations that uses RTTI...

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

if your instances all derive from a virtual base class, then you can reinterpret cast to that base class safely, and then dynamic cast to your other class...

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

class Object
{
   virtual ~Object() {}
};

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

calling A::IsOfThisClass(someData) will return true if someData is of class A.

如果someData属于A类,则调用A :: IsOfThisClass(s​​omeData)将返回true。

it's not the kind of hack you want to expose to a user, since it only works if void* points to a class derived from Object, but it can be a useful building block in controlled situations.

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