2017-01-25 64 views
3

我对类型转换的安全性有一些担忧我设计了一个抽象接口,女巫将由插件导出面向对象的C ABI ,即指向对象的指针和形式为func(void *this, ...)的C风格函数,而不是C++样式的成员函数,然后这些函数将被打包到表示对象实现的结构中。但是,我的一些底层框架使用多个虚拟继承。通过C ABI通过void指针传递C++对象(有可能的多重虚拟继承)

简单的例子

class A 
{ 
    public: 
     virtual void doA() 
} 

class B 
{ 
    public: 
     virtual void doB() 
} 

class C : public A, public B 
{ 
    public: 
     virtual void doA() 
     virtual void doB() 
} 

struct impA 
{ 
    (*doA)(void *self); 
} 

struct impB 
{ 
    (*doB)(void *self); 
} 

struct impC 
{ 
    (*doA)(void *self); 
    (*doB)(void *self); 
} 

void * AfromC(void *v) { 
    C*c = reinterpret_cast<C*>(v); // Known to be C* type 
    return static_cast<void*>(static_cast<A*>(c)); // method 1 
    return reinterpret_cast<void*>(static_cast<A*>(c)); // method 2 

    //method 3 & 4 
    C** c = static_cast<C**>(v); // Known to be C* type 
    return static_cast<void*>(&static_cast<A*>(*c)); // method 3 
    return static_cast<void*>(static_cast<A**>(c)); // method 4 
} 

/////////// main code 

class A 
{ 
    public: 
     void doA() { imp.doA(self); } 
    private: 
     impA imp; 
     void *self; 
} 

class B 
{ 
    public: 
     void doB() { imp.doB(self); } 
    private: 
     impB imp; 
     void *self; 
} 

考虑AfromC,我得到一个指针,我可以放心地通过C ABI的4个可能的方法,我想知道这些不同的方法的考虑,我更倾向于将方法1.

我不确定所有这些方法是否合法或安全。

注:对象永远是由它们创造了二元函数访问/摧毁他们返回/接受他们处理自己或C型数据类型(最多POD的结构)

其他对象虽然我发现在网上提到这些东西,但它们都是关于由于转换为无效而导致出现问题的人,即A* a= static_cast<A*>(static_cast<void*>(c)) // c -> C*,这是可以预料的,因为这不会纠正vtable,解决方案是使用摘要基本类型(这对我来说工作,因为我需要通过C ABI),但是我也听说虚拟指针比正常指针大,因此我考虑方法3和4的原因,因为这将是指向较大指针的正常指针,因此即使对于类型也是安全的与更大的指针。

所以,我的主要问题是方法1将出问题吗?我可以安全地定义一个模板函数沿着template <typename T, typename U> void * void_cast(U v) { static_cast<void *>(static_cast<T>(v)); }的行来简化插件代码。最后如果方法1是正确的,为什么?并且可以使用任何方法?

回答

1

的规则是,你可以来回转换,从一个指向对象的指针,以它的基类,并从一个指向对象为void *。但是不能保证所有这些指针保持相同的值(甚至不是相同的表示)!

说不同地其中C是从A衍生的例子:

C* c = new C; 
A* a = static_cast<A*>(c); // legal 
C* c1 = static_cast<C*>(a); // ok c1 == c guaranteed 

void *vc = static_cast<void *>(c); // legal 
C* c2 = static_cast<C*>(vc); // ok, c2 == c guaranteed 

void *va = static_cast<void *>(a); // legal, but va == vc is not guaranteed 

a2 = static_cast<A*>(vc); // legal, but a2 == a not guaranteed 
          // and dereferencing a2 is Undefined Behaviour 

这意味着,如果v被构建为void *v = static_cast<void *>(c);然后传递给你的AfromC方法static_cast<A*>(v)可能不会指向一个有效的对象。由于您从void*投射到指向obj和指向obj的指针,并且需要获取原始值,所以方法(1)和(2)都无效。

对于方法(4),但是您将指向void的指针指向指向指针的指针,从指针指向指向指针的指针,然后再返回到void。如3.9.2化合物类型[basic.compound]宣称:

3 ...指针布局兼容的类型应具有相同的值表示和 对准要求...

由于所有指针是布局兼容的类型,第二操作不应该改变的值,并且我们回到了方法的无操作(1)和(2)

方法(3)甚至不应该编译,因为你把static_cast的地址,这不是一个左值。 (1),(2)和(4)是无操作的,这意味着你返回的输入值不变,方法(3)是非法的,因为&操作符需要一个左值。

唯一realiable方式一个void *指向一个C对象转换为东西可以安全地转换为A *是:

void * AfromC(void *v) { 
    C* c = static_cast<C*>(v); // v shall be static_cast<void>(ptr_to_C) 
    A* a = static_cast<A*>(c); 
    return static_cast<void *>(a); // or return a; with the implicit void * convertion 
} 

或作为一行表达

void * AfromC(void *v) { 
    return static_cast<A*>(static_cast<C*>(v)); 
} 
+0

你能否澄清一下,“但是va == vc不能保证”,因为我把va转换成a * = static_cast (va),然后我可以将它转换成C类型的对象,即static_cast (a)或reinterpret_cast (a),因为我知道va已被转换为y我们从C类型的对象中得到答案,或者在通过void类型投射后,这会不再安全吗? – glenflet

+0

“dereferencing a2是未定义的行为”谨慎解释你为什么这么想?从我所知道的情况来看,在通用视图中,“a2”和“c2”与“a”和“c”是相同的,所以它们应该同样是UB--无论是yes还是no,不是中途。 – dascandy

+0

@glenflet:不能保证可以在标准中找到。在使用虚拟功能的vtables的实现中,通常对象的地址是虚拟表的地址。在这种情况下,static_cast进行偏移校正,但是'va'和'vc'的实际值在这种情况下是不同的。只要尝试使用调试器和具有虚函数的基类和子类 –

0
return static_cast<void*>(static_cast<A*>(v)); // method 1 
return reinterpret_cast<void*>(static_cast<A*>(v)); // method 2 

如果void* vC类型的实例,然后static_cast<A*>(v)是错误的。

//method 3 & 4 
C** c = static_cast<C**>(v); // Known to be C* type 
return static_cast<void*>(&static_cast<A*>(*c)); // method 3 
return static_cast<void*>(static_cast<A**>(c)); // method 4 

如果void* vC类型的实例,然后static_cast<C**>(v)是错误的。


非常小心铸造void*到正确类型的指向对象。当我可以时,我宁愿使用static_cast,而不是reinterpret_cast。我也倾向于隐式强制转换为基础子对象访问并转换为void*。简化的样板对眼睛不那么紧张。

void* AfromC(void* v) { 
    C* c = static_cast<C*>(v); // Known to be C* type 
    A* a = c;     // point to base sub object 
    return a;     // implicit conversion to void* 
}