Created
August 9, 2022 01:44
-
-
Save Wanger-SJTU/1117ba1eb265618e098584885640dbd7 to your computer and use it in GitHub Desktop.
access private var in c++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Bank { | |
| int money = 999'999'999; | |
| public: | |
| int getMoney() { | |
| return money; | |
| } | |
| template<typename T> void f(T& t) {} // exploit this to steal money | |
| }; | |
| class Thief; | |
| template<> void Bank::f<Thief>(Thief&); | |
| class Thief { | |
| public: | |
| int money = 0; | |
| void steal(Bank& bank) { | |
| bank.f(*this); | |
| } | |
| }; | |
| template<> | |
| void Bank::f<Thief>(Thief& thief) { | |
| thief.money = money; | |
| money = 0; | |
| } | |
| int main() { | |
| Bank bank; | |
| Thief thief; | |
| cout << bank.getMoney() << ' ' << thief.money << endl; // 999999999 0 | |
| thief.steal(bank); | |
| cout << bank.getMoney() << ' ' << thief.money << endl; // 0 999999999 | |
| return 0; | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
正常做法
如果类(私有变量所在的类)的代码可以修改,做法是添加 getter/setter 或者添加友元声明。
如果类的代码不可以修改,GotW #76: Uses and Abuses of Access Rights 这里提到了几种方法。这几种方法中,除了最后一种,其他方法都是非标准的。而最后一种方法的前提是,私有变量所在的这个类原先定义有成员函数模板,然后通过特化这个函数模板而获得对私有变量的访问权(成员函数模板特化后还是类的成员,自然有对私有成员的访问权),具体例子如下。
所以,如果类 Bank 原先没有那个成员函数模板 templatevoidf(T& t){},这种方法也就行不通了,等于说,还是要修改类的代码,去添加一个成员函数模板,那倒不如正规地添加 getter/setter 了。
模板黑魔法做法
所以真的没有在不能修改类的代码的限制下又符合标准的方法来访问私有成员吗?嗯,还真有。
@WhoTFAmI
和
@法号桑菜
都提到用模板的黑魔法,这类方法的核心是,模板显式实例化会忽略成员访问说明符,不会执行访问控制检查。
显式实例化函数模板
用 godbolt 查一下上面实例化得到的汇编(x86-64 gcc 11.2,-O1):
就一句
move指令,传送寄存器rdi的值到寄存器rax。根据调用约定,rdi保存的是参数,这里是一个Bank类的对象的起始地址,而返回值记录在rax中,这里要返回的是该对象中money成员的起始地址,而money刚好是类Bank的第一个成员,偏移刚好为 0,所以直接复制rdi到[rax](https://www.zhihu.com/search?q=rax&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A2399058597%7D) 就行了。可以在成员money之前添加其他字段,再看看得到的汇编有何不同,就能更加清楚了。总而言之,上面的 steal 函数能帮我们正确地算出一个
Bank类的对象的money成员的地址,就实现了对私有的money成员的访问。但可惜的是,我们没办法调用显式实例化函数模板得到的函数!模板显式实例化得到的实例是无法使用的,但如果显式实例化的动作有副作用呢?函数模板相对比较局限,所以下面我们使用类模板来说明。有副作用的显式实例化
上面程序会输出 A's Ctor。同样的,用 godbolt 查一下生成的汇编代码(x86-64 gcc 11.2,-std=c++17 -O1):
所以,进亿步:
(上面代码魔改自:Accessing Private Data)
改进 Thief,基于另外一种形式的副作用
(上面代码魔改自
@WhoTFAmI
的回答)
核心原理还是模板显式实例化时不会执行访问控制检查。同样遇到的限制是模板显式实例化得到的实例是无法使用的,但这里的解决方法是通过友元函数声明来生成全局函数。所以这里的副作用是全局函数的生成。
为何友元函数声明会生成全局函数,详见 Friend declaration 下关于第二种语法(friend function-definition)的具体描述。
同样,还是用 godbolt 查一下生成的汇编代码,但这次不要开 -O1 优化,因为友元声明定义的全局函数默认是 inline 的,开了会被内联掉(x86-64 gcc 11.2,-O0)
可见,显式实例化类模板 Thief 确实生成了一个全局函数 steal。不过因为不能开优化,生成的汇编代码不够简洁,但从标了 # 的两条指令来看,不难发现其实就是 mov rax, rdi,具体含义前文也有分析过。