C++ 面向对象的一大特性就是封装,使用不同的访问控制符来控制外接对其的访问权限。比如:



1 class A
2 {
3 public:
4 A(): i(10){}
5 void print(){ cout << "A::i = " << i << endl; }
6
7 private:
8 int i;
9 };





      这里的A 类对象的 i 对外接来说就是透明的。通过private 来隐藏对象的某些属性。但是,C++也继承了大部分C的特性,比如说很好很强大的指针。利用指针,我们可以绕过编译器做一些 见不得光的事情。虽然不建议这么做,因为这样不仅破坏了数据的封装特性,也会带来很多危险。不过这里我还是这么做了。。。。。

请看下面的这个例子,利用指针p去修改 对象a 的私有成员i 的值:



#include <iostream>
using namespace std;

class A
{
public:
A(): i(10){}
void print(){ cout << "A::i = " << i << endl; }

private:
const int i;
};

int main()
{
A a;

int* p = (int*)&a; // 突破编译器的防线
a.print();

*p = 30; // 偷偷修改a 对象私有成员的值
a.print();

return 0;
}


执行结果:



A::i = 10
A::i = 30


通过打印结果可以看出p 真的做了不该做的事情

这时候有的朋友可能会觉得将A 类中的 i 声明成 const int 指针p应该就无能为力了吧。

事实证明 私有成员变量即使是 const 类型也是无济于事的,他们都无法阻挡指针的穿透力!


我们再来看看其他的方式。


 


1.伪造者方式


这个伎俩是现将某个有待伪造的类定义复制一份,然后通过该复制后的“赝品”来达到目的,且看



void Hijack(X &x)
{
x.m_nPrivate = 2;
}
class X
{
// 手工添加
friend ::Hijack(X &);
// 这里是复制X类定义
private:
int m_nPrivate;
public:
X()
: m_nPrivate(1)
{}
template<typename T>
void Func(const T &t)
{}
const int GetValue()
{
return m_nPrivate;
}
};


这个伎俩被VC2008的编译器逮住了,没有编译通过。因为他违反了唯一定义规则(ODR,One Definition Rule)。看来语言律师还是不会放过这种没脑子的造假者!打假、打假,越打越假!


 


2. 偷窃者方式


偷偷的改变定义类的含义。且看:



#define private public    // 万恶的宏伎俩啊
void Hijack(X &x)
{
x.m_nPrivate = 2;
}


他的两根手指头很灵活哟。在VC2008成功执行得到。然而他却有两个违背标准的行为:

1)#define 保留字是非法的

2)违反了但一定以规则(ODR),然而类的底层内存布局没改变,故可行

3.骗子方式



// 同X的内存布局,只有一个int型的变量
class BaitSwitch
{
public:
int m_nNotPrivate;
};
void Func(X &x)
{
(reinterpret_cast(x)).m_nNotPrivate = 2;
}


在VC2008上成功运行达到目的,但是却有漏洞:


标准中reinterpret_cast的行为未定义,VC2008允许返回的结果引用。所以也成功让骗子得逞。


 


4.语言律师方式


律师就是钻法律的漏洞,永远也不会被逮住,他是在钻法律的空子!且看



namespace
{
struct Y{};
}
template<>
void X::Func(const Y&)
{
m_nPrivate = 2;
}
void Test()
{
X x;
cout << x.GetValue() << endl;
x.Func(Y());
cout << x.GetValue() << endl;
}


他能成功主要是利用了X具有一个成员模板的事实,代码完全符合标准,标准也确保这种行为会按照编码者的意图行事。boost和loki中大量运用此手法。


 


看法:


我相信这并不是C++访问控制机制的漏洞,或许,我们本不应该这样做。用成员模板提供一种有效的访问似有成员数据可以绕过累的访问控制,这也许就是我们想达到的目的。