Доступ к закрытой функции C++ с помощью указателей

Скажи, что у меня есть следующее:

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

Теперь, как я могу получить доступ int A::f() если мне дают указатель на объект? Я знаю, как получить!

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

Но теперь уверен, как запустить A::f().

ОБНОВЛЕНИЕ: я знаю, что это не хороший дизайн, и рядовые, как предполагается, скрыты. Вопрос только в том, чтобы узнать, как класс помещается в память в компиляторе.

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;
}

Demo.

Трюк для данных

Ваш трюк основан на предположениях о расположении памяти вашего объекта. К счастью для вашего указанного примера, с вашим компилятором, похоже, работает.

Если я скомпилирую этот код с 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. 

Но, конечно, это совершенно нестандартно и крайне опасно!

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

Хммм опять. Личные части класса должны быть частными. Это означает, что доступно только для методов этого класса, но не для других.

Если вы хотите сломать это, вы можете наверняка, но тогда вы нарушите также идею разработчиков интерфейса, который он хочет представить...

Другие вопросы по тегам