是的,我完全清楚我所询问的内容是完全愚蠢的,并且任何希望在生产代码中尝试此类内容的人都应该被解雇和/或出手。我主要想看看是否可以完成。
现在已经不在了,有没有办法从类外部访问C ++中的私有类成员?例如,有没有办法用指针偏移来做到这一点?
(天真和其他非生产准备技术欢迎)
正如评论中所提到的,我问了这个问题,因为我想写一篇关于过度封装的博客文章(以及它如何影响TDD)。我想看看是否有办法说“使用私有变量不是100%可靠的方法来强制封装,即使在C ++中也是如此。”最后,我决定更多地关注如何解决问题,而不是为什么这是一个问题,所以我没有像我原先计划的那样突出显示这里提到的一些东西,但我还是留下了一个链接。 / p>
无论如何,如果有人对它的出现感兴趣,那么它是:Enemies of Test Driven Development part I: encapsulation(我建议在你决定我疯了之前阅读它。)
答案 0 :(得分:67)
如果类包含任何模板成员函数,您可以专门化该成员函数以满足您的需要。即使原始开发人员没有想到它。
safe.h
class safe
{
int money;
public:
safe()
: money(1000000)
{
}
template <typename T>
void backdoor()
{
// Do some stuff.
}
};
main.cpp中:
#include <safe.h>
#include <iostream>
class key;
template <>
void safe::backdoor<key>()
{
// My specialization.
money -= 100000;
std::cout << money << "\n";
}
int main()
{
safe s;
s.backdoor<key>();
s.backdoor<key>();
}
输出:
900000
800000
答案 1 :(得分:51)
我添加了一个entry to my blog(见下文),展示了如何完成它。以下是如何将其用于以下类
的示例struct A {
private:
int member;
};
只需为它描述一个结构,然后实例化用于抢劫的实现类
// tag used to access A::member
struct A_member {
typedef int A::*type;
friend type get(A_member);
};
template struct Rob<A_member, &A::member>;
int main() {
A a;
a.*get(A_member()) = 42; // write 42 to it
std::cout << "proof: " << a.*get(A_member()) << std::endl;
}
Rob
类模板是这样定义的,只需定义一次,无论您计划访问多少私有成员
template<typename Tag, typename Tag::type M>
struct Rob {
friend typename Tag::type get(Tag) {
return M;
}
};
但是,这并未表明c ++的访问规则不可靠。语言规则旨在防止意外错误 - 如果您尝试抢夺对象的数据,则 by-design 语言不会花费很长时间来阻止您。
答案 2 :(得分:29)
以下是偷偷摸摸的,非法的,依赖于编译器的,并且可能无法工作,具体取决于各种实现细节。
#define private public
#define class struct
但它是你的OP的答案,你明确地邀请了一种技术,我引用它是“完全愚蠢的,并且任何希望在生产代码中尝试这样的事情的人都应该被解雇和/或开枪”
另一种技术是通过使用来自对象开头的硬编码/手动编码偏移来构造指针来访问私有成员数据。
答案 3 :(得分:24)
期待火花,也许是崩溃;)
答案 4 :(得分:12)
class A
{
int a;
}
class B
{
public:
int b;
}
union
{
A a;
B b;
};
应该这样做。
ETA:它适用于这种琐碎的课程,但作为一般情况,它不会。
TC ++ PL Section C.8.3:“具有构造函数,析构函数或复制操作的类不能是union成员的类型......因为编译器不知道要销毁哪个成员。”
所以我们最好的办法就是宣布class B
与A
的布局相匹配,然后查看班级的私人情况。
答案 5 :(得分:9)
如果您可以获得指向类成员的指针,则无论访问说明符是什么(偶数方法),都可以使用指针。
class X;
typedef void (X::*METHOD)(int);
class X
{
private:
void test(int) {}
public:
METHOD getMethod() { return &X::test;}
};
int main()
{
X x;
METHOD m = x.getMethod();
X y;
(y.*m)(5);
}
当然,我最喜欢的小黑客是后门的朋友模板。
class Z
{
public:
template<typename X>
void backDoor(X const& p);
private:
int x;
int y;
};
假设上面的创建者为他的正常用途定义了backDoor。但是您想要访问该对象并查看私有成员变量。即使将上面的类编译成静态库,您也可以为backDoor添加自己的模板特化,从而访问成员。
namespace
{
// Make this inside an anonymous namespace so
// that it does not clash with any real types.
class Y{};
}
// Now do a template specialization for the method.
template<>
void Z::backDoor<Y>(Y const& p)
{
// I now have access to the private members of Z
}
int main()
{
Z z; // Your object Z
// Use the Y object to carry the payload into the method.
z.backDoor(Y());
}
答案 6 :(得分:8)
使用C ++中的指针偏移量访问私有成员肯定是可能的。让我们假设我有以下类型定义,我想访问。
class Bar {
SomeOtherType _m1;
int _m2;
};
假设Bar中没有虚拟方法,那么简单的情况就是_m1。 C ++中的成员存储为对象的内存位置的偏移量。第一个对象位于偏移0处,第二个对象位于sizeof(第一个成员)的偏移处,等等......
所以这是一种访问_m1的方法。
SomeOtherType& GetM1(Bar* pBar) {
return*(reinterpret_cast<SomeOtherType*>(pBar));
}
现在_m2有点困难了。我们需要从原始字节移动原始指针sizeof(SomeOtherType)字节。转换为char是为了确保我以字节偏移量递增
int& GetM2(Bar* pBar) {
char* p = reinterpret_cast<char*>(pBar);
p += sizeof(SomeOtherType);
return *(reinterpret_cast<int*>(p));
}
答案 7 :(得分:4)
这个答案基于@Johannes's answer/blog所展示的确切概念,因为这似乎是唯一的“合法”方式。我已将该示例代码转换为方便的实用程序。它很容易与C ++ 03兼容(通过实现std::remove_reference
&amp;替换nullptr
)。
#define CONCATE_(X, Y) X##Y
#define CONCATE(X, Y) CONCATE_(X, Y)
#define ALLOW_ACCESS(CLASS, TYPE, MEMBER) \
template<typename Only, TYPE CLASS::*Member> \
struct CONCATE(MEMBER, __LINE__) { friend TYPE (CLASS::*Access(Only*)) { return Member; } }; \
template<typename> struct Only_##MEMBER; \
template<> struct Only_##MEMBER<CLASS> { friend TYPE (CLASS::*Access(Only_##MEMBER<CLASS>*)); }; \
template struct CONCATE(MEMBER, __LINE__)<Only_##MEMBER<CLASS>, &CLASS::MEMBER>
#define ACCESS(OBJECT, MEMBER) \
(OBJECT).*Access((Only_##MEMBER<std::remove_reference<decltype(OBJECT)>::type>*)nullptr)
ALLOW_ACCESS(<class>, <type>, <member>);
ACCESS(<object>, <member>) = <value>; // 1
auto& ref = ACCESS(<object>, <member>); // 2
struct X {
int get_member () const { return member; };
private:
int member = 0;
};
ALLOW_ACCESS(X, int, member);
int main() {
X x;
ACCESS(x, member) = 42;
std::cout << "proof: " << x.get_member() << std::endl;
}
答案 8 :(得分:4)
using namespace std;
class Test
{
private:
int accessInt;
string accessString;
public:
Test(int accessInt,string accessString)
{
Test::accessInt=accessInt;
Test::accessString=accessString;
}
};
int main(int argnum,char **args)
{
int x;
string xyz;
Test obj(1,"Shit... This works!");
x=((int *)(&obj))[0];
xyz=((string *)(&obj))[1];
cout<<x<<endl<<xyz<<endl;
return 0;
}
希望这有帮助。
答案 9 :(得分:3)
如果你知道你的C ++编译器如何破坏名字,是的。
除非我认为,这是一个虚拟功能。但是,如果你知道你的C ++编译器如何构建VTABLE ......
编辑:查看其他回复,我意识到我误解了这个问题并认为它是关于成员函数,而不是成员数据。但是,重点仍然是:如果您知道编译器如何布局数据,那么您可以访问该数据。答案 10 :(得分:1)
实际上很容易:
class jail {
int inmate;
public:
int& escape() { return inmate; }
};
答案 11 :(得分:1)
作为模板后门方法的替代方法,您可以使用模板后门类。不同之处在于您不需要将此后门类放入要测试的类的公共区域。我使用的事实是,许多编译器允许嵌套类访问封闭类的私有区域(这不完全是1998年的标准,但被认为是“正确的”行为)。当然,在C ++ 11中,这已成为合法行为。
见这个例子:
#include <vector>
#include <cassert>
#include <iostream>
using std::cout;
using std::endl;
///////// SystemUnderTest.hpp
class SystemUnderTest
{
//...put this 'Tested' declaration into private area of a class that you are going to test
template<typename T> class Tested;
public:
SystemUnderTest(int a): a_(a) {}
private:
friend std::ostream& operator<<(std::ostream& os, const SystemUnderTest& sut)
{
return os << sut.a_;
}
int a_;
};
/////////TestFramework.hpp
class BaseTest
{
public:
virtual void run() = 0;
const char* name() const { return name_; }
protected:
BaseTest(const char* name): name_(name) {}
virtual ~BaseTest() {}
private:
BaseTest(const BaseTest&);
BaseTest& operator=(const BaseTest&);
const char* name_;
};
class TestSuite
{
typedef std::vector<BaseTest*> Tests;
typedef Tests::iterator TIter;
public:
static TestSuite& instance()
{
static TestSuite TestSuite;
return TestSuite;
}
void run()
{
for(TIter iter = tests_.begin(); tests_.end() != iter; ++iter)
{
BaseTest* test = *iter;
cout << "Run test: " << test->name() << endl;
test->run();
}
}
void addTest(BaseTest* test)
{
assert(test);
cout << "Add test: " << test->name() << endl;
tests_.push_back(test);
}
private:
std::vector<BaseTest*> tests_;
};
#define TEST_CASE(SYSTEM_UNDER_TEST, TEST_NAME) \
class TEST_NAME {}; \
template<> \
class SYSTEM_UNDER_TEST::Tested<TEST_NAME>: public BaseTest \
{ \
Tested(): BaseTest(#SYSTEM_UNDER_TEST "::" #TEST_NAME) \
{ \
TestSuite::instance().addTest(this); \
} \
void run(); \
static Tested instance_; \
}; \
SYSTEM_UNDER_TEST::Tested<TEST_NAME> SYSTEM_UNDER_TEST::Tested<TEST_NAME>::instance_; \
void SYSTEM_UNDER_TEST::Tested<TEST_NAME>::run()
//...TestSuiteForSystemUnderTest.hpp
TEST_CASE(SystemUnderTest, AccessPrivateValueTest)
{
SystemUnderTest sut(23);
cout << "Changed private data member from " << sut << " to ";
sut.a_ = 12;
cout << sut << endl;
}
//...TestRunner.cpp
int main()
{
TestSuite::instance().run();
}
答案 12 :(得分:0)
受@Johannes Schaub-litb的启发,以下代码可能更容易理解。
struct A {
A(): member(10){}
private:
int get_member() { return member;}
int member;
};
typedef int (A::*A_fm_ptr)();
A_fm_ptr get_fm();
template< A_fm_ptr p>
struct Rob{
friend A_fm_ptr get_fm() {
return p;
}
};
template struct Rob< &A::get_member>;
int main() {
A a;
A_fm_ptr p = get_fm();
std::cout << (a.*p)() << std::endl;
}
答案 13 :(得分:0)
我使 Johannes answer 更通用。您可以在此处获取源代码:https://github.com/lackhole/Lupin
您只需要知道班级和成员的名称即可。
你可以使用喜欢,
#include <iostream>
#include "access/access.hpp"
struct foo {
private:
std::string name = "hello";
int age = 27;
void print() {}
};
using tag_foo_name = access::Tag<class foo_name>;
template struct access::Accessor<tag_foo_name, foo, decltype(&foo::name), &foo::name>;
int main() {
foo f;
// peek hidden data
std::cout << access::get<tag_foo_name>(f) << '\n'; // "hello"
// steal hidden data
access::get<tag_foo_name>(f) = "lupin";
std::cout << access::get<tag_foo_name>(f) << '\n'; // "lupin"
}
调用私有函数,获取私有成员的类型也可以只使用标签。
答案 14 :(得分:0)
好吧,有了指针偏移量,这很容易。困难的部分是找到偏移量:
class Foo
{
public:
int pub = 35;
private:
int foo = 5;
const char * secret = "private :)";
};
#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#include "other.hpp"
unsigned long long getPrivOffset(
const char * klass,
const char * priv,
const char * srcfile
){
std::ifstream read(srcfile);
std::ofstream write("fork.hpp");
std::regex r ("private:");
std::string line;
while(getline(read, line))
// make all of the members public
write << std::regex_replace(line, r, "public:") << '\n';
write.close();
read.close();
// find the offset, using the clone object
std::ofstream phony("phony.cpp");
phony <<
"#include <iostream>\n"
"#include <fstream>\n"
"#include \"fork.hpp\"\n"
"int main() {\n";
phony << klass << " obj;\n";
// subtract to find the offset, the write it to a file
phony <<
"std::ofstream out(\"out.txt\");\n out << (((unsigned char *) &(obj."
<< priv << ")) -((unsigned char *) &obj)) << '\\n';\nout.close();";
phony << "return 0;\n}";
phony.close();
system(
"clang++-7 -o phony phony.cpp\n"
"./phony\n"
"rm phony phony.cpp fork.hpp");
std::ifstream out("out.txt");
// read the file containing the offset
getline(out, line);
out.close();
system("rm out.txt");
unsigned long long offset = strtoull(line.c_str(), NULL, 10);
return offset;
}
template <typename OutputType, typename Object>
OutputType hack(
Object obj,
const char * objectname,
const char * priv_method_name,
const char * srcfile
) {
unsigned long long o = getPrivOffset(
objectname,
priv_method_name,
srcfile
);
return *(OutputType *)(((unsigned char *) (&obj)+o));
}
#define HACK($output, $object, $inst, $priv, $src)\
hack <$output, $object> (\
$inst,\
#$object,\
$priv,\
$src)
int main() {
Foo bar;
std::cout << HACK(
// output type
const char *,
// type of the object to be "hacked"
Foo,
// the object being hacked
bar,
// the desired private member name
"secret",
// the source file of the object's type's definition
"other.hpp"
) << '\n';
return 0;
}
clang++ -o main main.cpp
./main
输出:
private :)
您也可以使用reinterpret_cast
。
答案 15 :(得分:0)
仅限学习目的.... 尝试这....可能有帮助我猜... 该程序只需知道值...即可访问私人数据。
//GEEK MODE....;)
#include<iostream.h>
#include<conio.h>
class A
{
private :int iData,x;
public: void get() //enter the values
{cout<<"Enter iData : ";
cin>>iData;cout<<"Enter x : ";cin>>x;}
void put() //displaying values
{cout<<endl<<"sum = "<<iData+x;}
};
void hack(); //hacking function
void main()
{A obj;clrscr();
obj.get();obj.put();hack();obj.put();getch();
}
void hack() //hack begins
{int hck,*ptr=&hck;
cout<<endl<<"Enter value of private data (iData or x) : ";
cin>>hck; //enter the value assigned for iData or x
for(int i=0;i<5;i++)
{ptr++;
if(*ptr==hck)
{cout<<"Private data hacked...!!!\nChange the value : ";
cin>>*ptr;cout<<hck<<" Is chaged to : "<<*ptr;
return;}
}cout<<"Sorry value not found.....";
}
答案 16 :(得分:0)
以下代码使用指向该类的指针访问和修改类的私有成员。
#include <iostream>
using namespace std;
class A
{
int private_var;
public:
A(){private_var = 0;}//initialized to zero.
void print(){cout<<private_var<<endl;}
};
int main()
{
A ob;
int *ptr = (int*)&ob; // the pointer to the class is typecast to a integer pointer.
(*ptr)++; //private variable now changed to 1.
ob.print();
return 0;
}
/*prints 1. subsequent members can also be accessed by incrementing the pointer (and
type casting if necessary).*/
答案 17 :(得分:0)
我使用了另一种有用的方法(和解决方案)来访问c ++私有/受保护的成员 唯一的条件是您可以从要访问的班级继承 然后所有积分转到 reinterpret_cast&lt;&gt;()。
一个可能的问题是,如果插入一个虚拟函数,它将无法工作,这将修改虚拟表,因此,对象大小/对齐。
class QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QObject)
void dumpObjectInfo();
void dumpObjectTree();
...
protected:
QScopedPointer<QObjectData> d_ptr;
...
}
class QObjectWrapper : public QObject
{
public:
void dumpObjectInfo2();
void dumpObjectTree2();
};
然后你只需要按如下方式使用该类:
QObject* origin;
QObjectWrapper * testAccesor = reinterpret_cast<QObjectWrapper *>(origin);
testAccesor->dumpObjectInfo2();
testAccesor->dumpObjectTree2();
我最初的问题如下:我需要一个不会暗示重新编译QT库的解决方案。
QObject , dumpObjectInfo ()和 dumpObjectTree ()中有2种方法,
如果QT库是在调试模式下编译的,那么它们当然需要访问d_ptr被保护的成员(以及其他内部结构)。
我所做的是使用建议的解决方案在我自己的类( QObjectWrapper)中重新实现(复制和粘贴) dumpObjectInfo2 ()和 dumpObjectTree2 ()中的那些方法)删除那些调试预处理程序保护。
答案 18 :(得分:0)
类通常会为私有数据(getter和setter)提供mutator方法。
如果一个类确实提供了一个返回const引用(但没有setter)的getter,那么你可以只修改getter的返回值,并将其用作l值:
class A {
private:
double _money;
public:
A(money) :
_money(money)
{}
const double &getMoney() const
{
return _money;
}
};
A a(1000.0);
const_cast<double &>(a.getMoney()) = 2000.0;
答案 19 :(得分:0)
通过引用* this ,您可以为对象中的所有私有数据启用后门。
class DumbClass
{
private:
int my_private_int;
public:
DumbClass& backdoor()
{
return *this;
}
}
答案 20 :(得分:0)
“使用私有变量不是100%可靠的强制封装方式,即使在C ++中也是如此。” 真?您可以反汇编所需的库,找到所需的所有偏移量并使用它们。 这将使你能够改变你喜欢的任何私人成员......但是! 没有一些肮脏的黑客行为,你无法访问私人会员。 让我们说写 const 不会让你的常数保持不变,因为你可以 强制转换 const 或仅使用它的地址使其无效。如果您正在使用MSVC ++并且您为链接器指定了“-merge:.rdata = .data”,则该技巧将在没有任何内存访问错误的情况下工作。 我们甚至可以说用C ++编写应用程序并不是编写程序的可靠方法,因为在应用程序运行时,可能会从外部某处修补低级代码。 那么什么是可靠的文档化方法来强制封装?我们可以将数据隐藏在RAM中的某处,并防止除了我们的代码之外的任何内容访问吗?我唯一的想法是加密私人成员并备份它们,因为有些东西可能会破坏这些成员。 对不起,如果我的答案太粗鲁,我不是故意冒犯任何人,但我真的不认为这种说法是明智的。
答案 21 :(得分:0)
因为你有一个必需类的对象我猜你有类的声明。 现在您可以做的是声明另一个具有相同成员的类,但将所有访问说明符保留为public。
例如,上一课是:
class Iamcompprivate
{
private:
Type1 privateelement1;
Typ2 privateelement2;
...
public:
somefunctions
}
你可以将一个类声明为
class NowIampublic
{
**public:**
Type1 privateelement1;
Type2 privateelement2;
...
somefunctions
};
现在您需要做的就是将类Iamcompprivate
的指针转换为类NowIampublic
的指针,并将它们用作U愿。
示例:
NowIampublic * changetopublic(Iamcompprivate *A)
{
NowIampublic * B = (NowIampublic *)A;
return B;
}
答案 22 :(得分:0)
向所有人建议“ #define private public ”:
这种事情非法。标准禁止定义/取消在词法上等同于保留语言关键字的宏。虽然你的编译器可能不会抱怨(我还没有看到编译器这样做),但它并不是一件“好事”。
答案 23 :(得分:0)
只需创建自己的访问成员函数来扩展类。
答案 24 :(得分:0)
除了 #define private public ,您还可以 #define private protected ,然后将一些foo类定义为所需类的后代,以便能够访问它(现在受保护的)方法通过类型铸造。
答案 25 :(得分:-1)
class Test{
int a;
alignas(16) int b;
int c;
};
Test t;
方法A:侵入性情绪。 因为我们可以访问源代码并重新获取它,我们可以使用 许多其他方式,如朋友类访问私人会员,他们都是合法的后门。
方法B:蛮横情绪。
int* ptr_of_member_c = reinterpret_cast<int*>(reinterpret_cast<char*>(&t) + 20);
我们使用一个幻数(20),它并不总是正确的。当Test类的布局发生变化时,幻数是一个很大的bug源。
方法C:超级黑客情绪。 有没有非侵入性和非暴力的情绪? 因为类的Test的布局信息被编译器隐藏了, 我们无法从complie的口中获得偏移信息。 恩。
offsetof(Test,c); //complie error. they said can not access private member.
我们也无法从类Test获取成员指针。 恩。
&Test::c ; //complie error. they said can not access private member.
@Johannes Schaub - litb有一个博客,他找到了抢劫私人成员指针的方法。
但我认为这应该是编译器的错误或语言陷阱。
我可以在gcc4.8上编译它,但不能在vc8编译器上编译。
所以结论可能是: 房东建造所有后门。 小偷总是有暴力和坏的方式闯入。 黑客偶然有优雅和自动化的方式进入。