1、reinterpret_cast
不知道Adboe为什么总爱问reinterpret_cast这个很不安全的操作符的问题,既然人家问,就要搞清楚吧
reinterpret_cast的不安全,在于它是进行比特位的拷贝,例
int n=9;
double d=reinterpret_cast<double & > (n); //这里不能用<double>编译会报错,似乎编译器认定<>里不可以有非指针类型(引用可以)。
结果就会出问题。原因 将整形转化成double引用,没有进行相应的二进制转化,只是把比特位简单拷贝。
为了验证以上的结论,做个试验,在上述两行代码下加行代码
int r=static_cast<int>d; //输出结果是8000 0000
然而当加如下代码时
int r=reinterpret_cast<int&>(d);//注意<>里还只能是引用
结果r=9;
注意:
A)这里提下static_cast,这个运算符可以转换类型,也可以转换指针,但是指针必须是基类和继承类,如果是double*和int*,会报编译错误,所以上述转换必须用reinterpret_cast。实际上用reinterpret_cast<>转化后的指针地址是一样的,不过切记指针类型已不同了,否则虽然地址相同,但解析成不同的指针类型,读到的最终结果就会出问题。例
int n=9;
int pInt=&n;
double pDouble=reinterpret<double*>pInt; //这里static_cast<>转化会报编译错误。但(double*)却可以。
B)这里顺便提下dynamic_cast,他的<>里可以是引用或指针。另外要注意 :使用dynamic,类里要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表
reinterpret_cast的两个应用
(1)面试官问我输出结果有什么不同?我试了下,结果相同.
class B{
public:
int i;
virtual void f(){}
B(){i=1;}
};
class D:public B{
public:
int j;
void f(){}
D(){i=1;j=2;}
};
void main(){
B* b1,*b2, *b3;
b1=new D;
b2=new D;
b3=new D;
D*d1,*d2,*d3;
d1=reinterpret_cast<D*>(b1);
d2=dynamic_cast<D*>(b2); //问题的本质,在于不可能把一个基类指针转化成一个继承类指针然后访问根本没有的继承类数据成员
d3=static_cast<D*>(b3);
}
(2)面试官让我写个在不了解类结构的情况下,给某个数据成员地址得到其所属结构地址和反向操作的函数,我的代码如下
class A{
public:
A(int i,int j):mi(i),mj(j){} //注意,我开始并不知道类中有多少数据成员
void atoj(A&S){cout<<&S.mj;} //由结构名和数据名得到数据成员地址,这个功能实现很简单
void jtoi(int &i) //有数据地址,数据名得到结构地址
{
A S(2,3); /*没有默认的构造函数,所以直接写S就不行了*/
int l=&S.mi-reinterpret_cast<int *>(&S); /*需要强制类型转换reinterpret_cast的用武之地*/
//调试反馈中间结果cout<<l<<endl;
cout<<(&i-l)<<endl; //输出想要的struct结构的地址
}
void print(){cout<<this<<endl;cout<<&mi<<endl;} //显示正确结果以作比对
int mi;
int mj;
};
void main(){
A hack(1,2);
hack.jtoi(hack.mi);
hack.print();
} //这道题的思想体现在如果想得到某个数据在某个类中的偏移量,通过实例化一个对象就可以得到了,因为每个类的数据偏移量是相同的。这就涉及了对对象内存存储的理解,下面一道题就是体现对内存对象的理解的。
class A{
public:
A(int i,int j):mi(i),mj(j){}
void print(){cout<<mi;cout<<mj;}
private:
int mi;
int mj;
};
void steal(A &hack){ //编一个函数来偷私有数据
int *p=reinterpret_cast<int *>(&hack); //知道私有数据的地址,就可以得到和修改了
cout<<*p<<endl;
cout<<*(p++)<<endl;}
void main(){
A hack(1,2);
hack.print();
steal(hack);
}
最后补充一句,数组名、类名、和结构名的地址和里面第一个数据的地址是相同,他们就相当于一个特殊的变量名,编译器可以通过他们和偏移量来来找到数据罢了,这些名本身并不占内存空间。另外如果类中包含虚函数,要考虑跳过在类头部的虚函数表(4个字节)另外如果多重继承,会有不止一个虚函数表。
未完待续,欢迎批评指正~