C++ достъп до частна функция чрез указатели

Да кажем, че имам следното:

class A {
private:
    int a;
    virtual int f() {return a;}
public:
    A(int t) {a = t;}
};

Сега, как да получа достъп до int A::f(), ако ми бъде даден указател към A обект? Знам как да получа!

void main () {
    A* x = new A(5);
    cout << ((int*)x)[2]; // returns 5;
}

Но сега сте сигурни как да стартирате A::f().

АКТУАЛИЗАЦИЯ: Знам, че това не е добър дизайн и личните данни трябва да бъдат скрити. Въпросът е само да знаете как класът се поставя в паметта на компилатора.


person sixer24    schedule 24.02.2015    source източник
comment
Какъв проблем се опитваш да решиш? Хакерското решение би било дефинирано изпълнение - намиране на указателя на vtable и след това намиране на правилния запис в таблицата.   -  person Drew Dormann    schedule 25.02.2015


Отговори (3)


Знам как да получа a!

A* x = new A(5);
cout << ((int*)x)[2]; // returns 5;

Това е такъв хак! Колкото и да е почти непреносим, ​​този код причинява недефинирано поведение, което се случва да доведе до резултата, който очаквате. Това не означава, че ще произведе същия резултат на друга система или дори на същата система, когато компилаторът избере да имплементира класа по различен начин.

Няма начин за достъп до частна функция, освен ако някой не ви даде указател към членска функция, която я препраща, или декларира вашата функция като friend:

class A;
typedef  int (A::*FPTR)();
class A {
private:
    int a;
    virtual int f() {return a;}
public:
    A(int t) {a = t;}
    FPTR get_private() { return &A::f; }
};
int main() {
    A a(123);
    FPTR fp = a.get_private();
    int res = (a.*fp)();
    cout << res << endl;
    return 0;
}

Демо.

person Sergey Kalinichenko    schedule 24.02.2015

Hmpf отново. Частните части на клас са предназначени да бъдат частни. Това означава, че е достъпен само от методи от този клас, а не от други.

Ако искате да нарушите това, можете със сигурност, но след това нарушите и идеята на внедрителя за интерфейса, който той иска да представи...

person pbhd    schedule 24.02.2015
comment
Може би трябва да помислите например за модулни тестове, където понякога е от решаващо значение да имате достъп до частни части, но предполагам, че винаги следвате книгата - person newhouse; 04.12.2017
comment
За unittests приетата практика е да имате добър приятел, напр. класът Fixture или нещо друго в зависимост от рамката, която използвате. - person pbhd; 28.12.2017

Трик за данните

Вашият трик се основава на предположения за разположението на паметта на вашия обект. За щастие за вашия конкретен пример, с вашия компилатор изглежда работи.

Ако компилирам този код с MSVC2013, той изобщо не работи!! В крайна сметка бих могъл да разбера, че работи, когато използвам масив от short вместо int, ако сложа някои съвети в конструктора:

A(int t) { a = t; cout << "A: " << (void*)this << " a: " << (void*)&a << " rel:" << (char*)&a - (char*)this << endl; }

Заключение: Не можете да разчитате на това като цяло (и дори не за вашия компилатор в по-сложни примери). Стандартът дава много ограничена гаранция за относителните адреси на паметта на членовете :

Раздел 9.2/15: Членовете с нестатични данни на клас (без обединение) със същия контрол на достъпа се разпределят, така че по-късните членове да имат по-високи адреси в обект на клас. Редът на разпределяне на нестатични членове на данни с различен контрол на достъпа не е определен. Изискванията за подравняване на изпълнението може да накарат два съседни члена да не бъдат разпределени непосредствено един след друг; (...)

Освен това вашият трик може да създаде псевдоним на указател. Така че компилаторът прави предположение за вашите указатели и членовете на вашия обект, които може да не са валидни, причинявайки неуспех на генерираните оптимизации.

Трик за функции

За адреса на вашата функция е още по-лошо. Това отново не е дефинирано в стандарта и е специфично за изпълнението.

Често използваната реализация за виртуални функции се основава на специфични за класа vtables. Всеки полиморфен обект има някъде в класа указател към своята vtable (обикновено в първите няколко байта на обекта.

Относителната позиция на указателя на функцията във vtable зависи от всички виртуални функции на класа и всички наследени класове. За вашия пример, с MSVC2013 мога да успея да извикам функцията:

typedef int (A::*fptr)();
fptr pf = (*(fptr**)x)[0];  // first unction in vtable
(x->*pf)();  // call it. 

Но разбира се това е напълно нестандартно и изключително опасно!

И имайте предвид, че за невиртуални функции изобщо не можете да получите адреса от обекта.

person Christophe    schedule 24.02.2015