问题:
如何访问类的私有成员? 前提是不留后门
</pre><pre name="code" class="cpp">class Test{
void f(){}
int m_;
};
解答1)
用友元。友元是最明显的后门,这个答案不算数。
解答2)
用成员指针。
class Test;
typedef void (Test::*PF)();
typedef int Test::* PM;
class Test
{
private:
void f(){}
int m_;
public:
PF getpf() { return &Test::f;}
PM getpm() { return &Test::m_;}
};
int main()
{
Test x;
PF pf = x.getpf();
PM pm = x.getpm();
(x.*pf)();
x.*pm = 10;
}
通过成员函数指针,可以不受私有的限制,任意调用。但是既然能改动源代码,为什么不直接开发public的访问函数呢?而且,
getpf()等类似东西是一种类似firend的后门。
这个解答也不可取。
解答3)
用模板函数
class Test
{
int m_;
public:
template <int Dummy> void backdoor(){}
};
template <>
void Test::backdoor<0>()
{
m_=0;
}
int main()
{
Test t;
t.backdoor<0>();
}
虽然很巧妙,但仍旧需要在Test类上开后门,这种“侵入式”修改不算黑客方式,更像哗众取宠。
解答4)
更加黑客的方式是,知道Test类的布局,从偏移地址计算成员的地址。
//#define OFFSET(Test, m_) ???
int main()
{
Test t;
char* start = reinterpret_cast<char*>(&t);
char* pm = start + 0;
int* pmi = reinterpret_cast<int*>(pm);
*pmi = 123;
}
我们知道Test中的成员m_的偏移地址是0,就可以计算t.m_的地址了。但是,这个0是魔术数字,当Test的布局发生变化,m_的偏移地址很可能就不是0了。
如果想更自动化的方式,例如 OFFSET(Test, m_) 自动计算偏移地址,就能避免使用魔术数字问题。
很可惜,当你发现一个 #define offsetof(s,m) (size_t)&(((s *)0)->m) 可能能用上时,却又发现offsetof(Test,m_)编译不过去,因为Test::m_是私有的。
解答5)
我拷贝一份相似代码, 只是把私有变成公有
<pre name="code" class="cpp">class Test{
private:
int m_;
};
class Test2{
public:
int m_;
};
int main()
{
Test t;
Test2& t2 = reinterpret_cast<Test2&>(t);
t2.m_=10;
}
此方案的缺点是把魔术字取消了,却引入一个魔术类。当Test发生变化后,Test2不会自动跟着变,因此会产生bug,甚至段错误。
解答6)
template<typename Tag>
struct result {
/* export it ... */
typedef typename Tag::type type;
static type ptr;
};
template<typename Tag>
typename result<Tag>::type result<Tag>::ptr;
template<typename Tag, typename Tag::type p>
struct rob : result<Tag> {
/* fill it ... */
struct filler {
filler() { result<Tag>::ptr = p; }
};
static filler filler_obj;
};
template<typename Tag, typename Tag::type p>
typename rob<Tag, p>::filler rob<Tag, p>::filler_obj;
struct A {
private:
void f() {
std::cout << "proof!" << std::endl;
}
};
struct Af { typedef void(A::*type)(); };
template class rob<Af, &A::f>;
int main() {
A a;
(a.*result<Af>::ptr)();
}
一种非侵入式,获取成员指针的方法。可能是利用编译器漏洞。
理论上说,不能随便就偷窃到私有成员的指针。但是这个模板方案就做到了。不排除将来编译器堵死这个漏洞的可能性。
总结:
研究私有成员的访问方法,实际上是考察意外的修改私有成员的可能性。通过穷思苦想,得到的结论是,私有成员的访问都不是意外的,甚至是故意的(如方案6)
通过暴力访问,总能修改任意内存中的数据(包括私有数据),不能考虑那些歪门邪道。我们只考虑在正常使用C++语言后,编译器能否帮助程序员检查出逻辑错误。
通过特殊手段访问私有数据,在编写单元测试程序时特别有利。单元测试要访问和调用私有函数和私有数据,如果是非侵入式的手段,将大大有利于代码的清晰性和可维护性