Доступ к частной функции С++ через указатели

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

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

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

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

person pbhd    schedule 24.02.2015
comment
Возможно, вам следует подумать, например, о модульных тестах, где иногда очень важно получить доступ к закрытым частям, но я полагаю, что вы всегда следуете правилам. - person newhouse; 04.12.2017
comment
Для юнит-тестов принято иметь хорошего друга, например. класс 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. Каждый полиморфный объект имеет где-то в классе указатель на его виртуальную таблицу (обычно в первых нескольких байтах объекта.

Относительное положение указателя функции в виртуальной таблице зависит от всех виртуальных функций класса и всех унаследованных классов. В вашем примере с MSVC2013 мне удалось вызвать функцию:

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

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

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

person Christophe    schedule 24.02.2015