Могу ли я получить доступ к закрытым членам извне класса без использования друзей?

Отказ от ответственности

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

Теперь, когда это исключено, есть ли способ получить доступ к закрытым членам класса в C++ извне класса? Например, есть ли способ сделать это с помощью смещения указателя?

(Приветствуются наивные и не готовые к производству техники)

Обновлять

Как отмечалось в комментариях, я задал этот вопрос, потому что хотел написать в блоге сообщение о чрезмерной инкапсуляции (и о том, как это влияет на TDD). Я хотел посмотреть, есть ли способ сказать, что «использование частных переменных не является на 100% надежным способом принудительной инкапсуляции даже в C++». В конце концов, я решил больше сосредоточиться на том, как решить проблему, а не на том, почему это проблема, поэтому я не упомянул некоторые из поднятых здесь вещей так широко, как планировал, но я все же оставил ссылку.

В любом случае, если кому-то интересно, как это получилось, вот оно: Враги разработки через тестирование, часть I: инкапсуляция (предлагаю прочитать, прежде чем вы решите, что я сошел с ума).

Из интереса, зачем задавать вопрос. Единственное, что я могу придумать для этого, - это взломать чужой API, чтобы нанести ущерб.

SmacL 08.01.2009 17:48

Я пишу в блоге сообщение о чрезмерной инкапсуляции. Я просто хотел посмотреть, можно ли сказать, что «защита частными методами не идеальна даже в C++!» Я отправлю ссылку, как только напишу.

Jason Baker 08.01.2009 18:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
73
2
70 673
25
Перейти к ответу Данный вопрос помечен как решенный

Ответы 25

Если вы знаете, как компилятор C++ изменяет имена, то да.

Если, я полагаю, это не виртуальная функция. Но тогда, если вы знаете, как ваш компилятор C++ строит VTABLE ...

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

Я считаю этот ответ интересным, потому что самый большой аргумент против использования закрытых членов Python заключается в том, что на самом деле это просто искажение имен (хотя и более стандартизованное искажение имен). Интересно знать, что в C++ все примерно так же.

Jason Baker 08.01.2009 19:39

Джейсон - разница в том, что в Python вам могут хлопнуть по запястью за доступ к частным лицам. В C++ ваши коллеги-разработчики будут обвинять вас в том, что вы так далеко пошли навстречу инкапсуляции.

Tom 09.01.2009 08:47

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

#define private public
#define class struct

Но это ответ на ваш OP, в котором вы явно приглашаете технику, которая, как я цитирую, «совершенно глупа и что любой, кто хотел бы попробовать такую ​​вещь в производственном коде, должен быть уволен и / или застрелен».


Другой способ - получить доступ к данным закрытого члена путем создания указателей с использованием жестко заданных / закодированных вручную смещений от начала объекта.

Вам также понадобится структура класса #define, иначе частное по умолчанию может помешать вам.

Tom 08.01.2009 16:36

Хаки препроцессора - это не больше доступа к закрытым членам, чем разыменование указателя.

cletus 08.01.2009 16:42

rq неверен в механизме (ослабление проверок доступа никогда не приводит к недействительности действующей программы из-за того момента, когда они происходят). Взлом нарушает ODR и может нарушить работу программы из-за искажения имени.

MSalters 08.01.2009 19:21

@MSalters он сломается (на vc2005) при попытке связать (сейчас) открытый конструктор с частным конструктором в файле .lib.

Aaron 09.01.2009 18:38

Я пробовал этот трюк, чтобы добавить модульные тесты в устаревший код со смешанными результатами. Казалось, что это работает для частных данных, но не для частных методов. Очевидно, компилятор, который я использовал (VC++ 9.0), включал видимость метода как часть схемы изменения его имени.

Ferruccio 04.07.2010 02:47

Также хорошо работал для TDD с использованием Xcode 6 (LLVM), этот ответ - лучшее решение для решения моей проблемы, без необходимости вообще изменять исходный код.

Kaa 02.11.2014 04:19

К вашему сведению, если вы работаете в мире Visual Studio, вы также должны добавить _XKEYCHECK_H в определения препроцессора.

sevensevens 27.01.2016 22:52

В некоторых случаях это не работает, потому что class можно использовать в параметре шаблона (то же самое, что и typename, но struct не может.

user202729 22.02.2020 07:44

Помимо #define private public вы также можете #define private protected, а затем определить некоторый класс foo как потомок требуемого класса, чтобы иметь доступ к его (теперь защищенным) методам через приведение типов.

Хммм, не знаю, сработает ли это, но, возможно, стоит попробовать. Создайте другой класс с тем же макетом, что и объект, с частными членами, но с частными, измененными на общедоступные. Создайте переменную указателя на этот класс. Используйте простое приведение, чтобы указать это на ваш объект с частными членами, и попробуйте вызвать частную функцию.

Ожидайте искр и, может быть, аварии;)

«с такой же планировкой». Это самая сложная часть. У компилятора есть большая свобода в том, как он размещает элементы в не-POD. Однако на практике это, вероятно, сработает.

Richard Corden 08.01.2009 16:35

@ Ричард Корден - Я тоже так думаю. Еще раз проиллюстрировав, что «вероятно, сработает» и «должно быть сделано в отдельном выпуске» - это совершенно разные вещи.

SmacL 08.01.2009 17:41

Имейте в виду, что компилятор совершенно бесплатно переупорядочивает элементы на основе контроля доступа (на самом деле, он совершенно бесплатно переупорядочивает, если есть какой-либо контроль доступа). Я не знаю, сколько на самом деле это делают ...

David Thornley 08.01.2009 23:39

Большинство компиляторов не меняют порядок членов - им не разрешено делать это для структур POD, иначе они нарушат совместимость со структурами C (например, struct sockaddr). На самом деле, очень мало стимулов для автоматического изменения порядка участников.

Tom 09.01.2009 08:50

просто создайте свою собственную функцию-член доступа для расширения класса.

Определенно возможно получить доступ к закрытым членам с помощью смещения указателя в C++. Предположим, у меня было следующее определение типа, к которому я хотел получить доступ.

class Bar {
  SomeOtherType _m1;
  int _m2;
};

Предполагая, что в Bar нет виртуальных методов, простой случай - _m1. Члены в C++ хранятся как смещения области памяти объекта. Первый объект находится со смещением 0, второй объект со смещением sizeof (первый член) и т. д.

Итак, вот способ доступа к _m1.

SomeOtherType& GetM1(Bar* pBar) {
  return*(reinterpret_cast<SomeOtherType*>(pBar)); 
}

Теперь с _m2 посложнее. Нам нужно переместить исходный указатель на размер (SomeOtherType) байтов из оригинала. Приведение к char должно гарантировать, что я увеличиваю байтовое смещение.

int& GetM2(Bar* pBar) {
  char* p = reinterpret_cast<char*>(pBar);
  p += sizeof(SomeOtherType);
  return *(reinterpret_cast<int*>(p));
}

Остерегайтесь проблем с заполнением и выравниванием памяти. Но +1 конечно выполнимо!

Mr.Ree 08.01.2009 23:06

Всем, кто предлагает "#define private public":

Это что-то вроде незаконный. Стандарт запрещает определение / отмену определения макросов, которые лексически эквивалентны ключевым словам зарезервированного языка. Хотя ваш компилятор, вероятно, не будет жаловаться (я еще не видел компилятора, который бы жаловался), это не то, что нужно делать.

Но вы заметите, что вопрос не требует, чтобы это было «хорошо». Фактически, хорошего способа сделать это может не существовать.

Jason Baker 08.01.2009 19:27

Ну, вопрос делает требует, чтобы метод работал, верно? Поскольку стандарт запрещает подобные вещи, метод, описанный другими, просто нарушен по определению. Это работает, потому что большинство компиляторов не применяют этот правило.

Tritium 08.01.2009 19:35

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

Johannes Schaub - litb 08.01.2009 22:23

(и в том числе запретить вам доступ к личным - что, по иронии судьбы, именно то, чего хочет вопрошающий нет). Насколько я знаю, разрешено #define ключевые слова, когда нет стандартного заголовка, включенного в ту же единицу перевода. но, как я уже сказал, это не может быть решением!

Johannes Schaub - litb 08.01.2009 22:28

litb, ваша логика не совсем верна. Явно ЗАПРЕЩАЕТСЯ определять имя макроса, которое лексически идентично ЛЮБОМ зарезервированному ключевому слову, независимо от того, что вы включаете в единицу перевода. Это очень просто: «не делай этого, это незаконно».

Tritium 08.01.2009 23:26

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

Tom 09.01.2009 08:52

1. Учитывая тот факт, что люди действительно ответили на это предложение, в первую очередь говорит о том, что они не знали, что это было незаконно. 2. По вашей логике, люди также будут разыменовывать нулевые указатели, записывать границы массива и т. д., Потому что это происходит «срабатывает» на их машинах.

Tritium 09.01.2009 18:28

На самом деле это довольно просто:

class jail {
    int inmate;
public:
    int& escape() { return inmate; }
};

Я предполагаю, что OP хочет доступ без изменения самого определения класса.

j_random_hacker 09.01.2009 12:51

Для разработки, основанной на тестировании, такие бэкдоры могут иметь смысл. Их легко удалить с помощью #define. # определите, что беспорядок с публичным / частным может повлиять на семантику программы, например POD-ность.

MSalters 09.01.2009 15:56
Ответ принят как подходящий

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

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

Существует вероятность того, что ключ будет конфликтовать. Поместите его в анонимное пространство имен.

Martin York 12.01.2009 20:08

Если вы можете получить указатель на член класса, вы можете использовать указатель независимо от спецификаторов доступа (даже методов).

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

@ previouslyknownas_463035818 Только из метода, имеющего доступ. Если у метода есть доступ к методу, он может получить указатель на функцию и вернуть его. Когда у вас есть указатель, его можно передавать и использовать, поскольку для этого указателя нет разрешений.

Martin York 03.09.2019 16:49

@ firstlyknownas_463035818 1: test() - это private, и значение, возвращаемое getMethod() (то есть public), может быть любым. 2: Нет, вы не можете получить указатель извне.

Martin York 03.09.2019 18:51

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

largest_prime_is_463035818 04.09.2019 22:16
class A 
{ 
   int a; 
}
class B
{
   public: 
   int b;
}

union 
{ 
    A a; 
    B b; 
};

Это должно сработать.

ETA: Это будет работать для такого тривиального класса, но в целом - нет.

TC++PL Section C.8.3: "A class with a constructor, destructor, or copy operation cannot be the type of a union member ... because the compiler would not know which member to destroy."

Итак, нам остается лучше всего объявить class B, чтобы он соответствовал макету A, и взломать, чтобы посмотреть на рядовые классы.

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

Например, предыдущий класс:

class Iamcompprivate
{
private:
    Type1 privateelement1;
    Typ2 privateelement2;
    ...

public:
    somefunctions
}

вы можете объявить класс как

class NowIampublic
{
**public:**
    Type1 privateelement1;
    Type2 privateelement2;
    ...

    somefunctions
};

Теперь все, что вам нужно сделать, это преобразовать указатель класса Iamcompprivate в указатель класса NowIampublic и использовать их по своему усмотрению.

Пример:

NowIampublic * changetopublic(Iamcompprivate *A)
{
    NowIampublic * B = (NowIampublic *)A;
    return B;
}

Неопределенное поведение не определено

Barry 07.05.2018 18:00

Я добавил запись в мой блог (см. Ниже), который показывает, как это можно сделать. Вот пример того, как вы используете его для следующего класса

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++ ненадежны. Правила языка разработаны для защиты от случайных ошибок - если вы попытаетесь украсть данные объекта, язык по дизайну не займет много времени, чтобы помешать вам.

Разве это не должно быть «языковые правила НЕ ...»?

user195488 27.02.2013 02:32

Кажется, это не работает. Он работает с примером частной функции в вашем блоге, но с int я получаю: error: аргумент шаблона без типа типа int A :: * должен иметь целочисленный или перечисляемый тип (для класса шаблона rob <Amem, & A: : member>;)

chris 30.06.2014 12:36

@chris, я рекомендую задать вопрос по этому поводу. я не могу помочь с комментариями

Johannes Schaub - litb 09.07.2014 21:34

Я использую это: struct Am {typedef int A :: * type; }; Он работал на GCC4.8. Думаю, в этом методе используется ошибка компилятора. Имеет проблемы с переносимостью.

thomas 11.09.2014 05:30

@thomas, если вы прочтете мой пост в блоге, вы поймете, что это не ошибка компилятора. Тем не менее, я рекомендую использовать мой следующий пост в блоге. Поскольку это технически лучшая реализация эксплойта. bloglitb.blogspot.de/2011/12/…

Johannes Schaub - litb 11.09.2014 21:49

Комитет должен очень сожалеть. Что сказал Херб Саттер? «не делайте этого в реальном коде». Комитету следует отнестись к этому серьезно. Ваш метод нас действительно шокирует. хорошая работа @ Johannes Schaub - litb

thomas 14.09.2014 11:38

Спасибо за этот ответ. Это было полезно для меня при использовании автоматически созданной библиотеки google protobuf. :-) В мой ответ я преобразовал ваш код в универсальную утилиту для частого использования (хотя, возможно, она и не так хороша). @Puppy, нечего смущаться, иногда это необходимо. До того, как прочитать этот ответ (еще в июле 2016 года), я даже не подозревал, что такое возможно. Даже не знал, что через 6 месяцев буду использовать.

iammilind 01.01.2017 18:27

Обратите внимание на два основных момента, если ответ покажется вам сложным: 1) template struct Rob<A_member, &A::member>; Это явное создание экземпляра класса шаблона. &A::member работает здесь так как: «Явные определения экземпляров игнорируют спецификаторы доступа к членам: типы параметров и возвращаемые типы могут быть закрытыми»

bartolo-otrit 02.05.2017 15:48

2) a.*get(A_member()) вызывает функцию друга, не являющуюся членом, get(A_member), которая возвращает &A::member. Это работает из-за "инъекция друга". Попробуйте удалить «бесполезный» аргумент функции get (), и вы должны получить «get не был объявлен в этой области» (я хотел бы увидеть краткое объяснение этого поведения).

bartolo-otrit 02.05.2017 15:50

Есть ли какие-либо модификации для случая, когда A::member не является int, а enum Foo, где имя Foo является частным в A?

M.M 22.02.2018 08:21

«использование частных переменных не является на 100% надежным способом принудительной инкапсуляции даже в C++». Действительно? Вы можете разобрать нужную библиотеку, найти все необходимые смещения и использовать их. Это даст вам возможность изменить любого приватного участника, который вам нравится ... НО! Вы не можете получить доступ к частным участникам без грязного взлома. Допустим, запись const не сделает вашу константу действительно постоянной, потому что вы можете отбросьте const или просто используйте его адрес, чтобы сделать его недействительным. Если вы используете MSVC++ и указали компоновщику "-merge: .rdata = .data", трюк будет работать без каких-либо ошибок доступа к памяти. Мы даже можем сказать, что написание приложений на C++ не является надежным способом написания программ, потому что результирующий низкоуровневый код может быть исправлен откуда-то извне, когда ваше приложение работает. Тогда каков надежный документированный способ принудительной инкапсуляции? Можем ли мы спрятать данные где-нибудь в ОЗУ и запретить доступ к ним кому-либо, кроме нашего кода? Единственная идея, которая у меня есть, - это зашифровать частные члены и сделать их резервную копию, потому что что-то может повредить этих членов. Извините, если мой ответ слишком груб, я не хотел никого обидеть, но я действительно не думаю, что это утверждение разумно.

Вопрос в самом языке, а не в особенностях платформы, которые вы можете или не можете делать.

Ruslan 19.08.2016 17:49

классный вопрос кстати ... вот моя штука:

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

Надеюсь это поможет.

Обращаясь к * это, вы активируете бэкдор для всех личных данных в объекте.

class DumbClass
{
private:
    int my_private_int;
public:
    DumbClass& backdoor()
    {
        return *this;
    }
}

В качестве альтернативы методу бэкдора шаблона вы можете использовать класс бэкдора шаблона. Разница в том, что вам не нужно помещать этот бэкдор-класс в общедоступную часть класса, который вы собираетесь тестировать. Я использую тот факт, что многие компиляторы позволяют вложенным классам получать доступ к частной области включающего класса (что не совсем стандарт 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();
}

Довольно часто класс предоставляет методы-мутаторы для частных данных (геттеры и сеттеры).

Если класс предоставляет геттер, который возвращает ссылку на константу (но не сеттер), тогда вы можете просто const_cast возвращать значение геттера и использовать его как 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;

Я использовал другой полезный подход (и решение) для доступа к частному / защищенному члену C++. Единственное условие - вы можете наследовать от класса, к которому хотите получить доступ. Тогда все заслуги передаются reinterpret_cast <> ().

Возможная проблема заключается в том, что это не сработает, если вы вставите виртуальную функцию, которая изменит виртуальную таблицу и, следовательно, размер / выравнивание объекта.

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 (среди других внутренних структур). Что я сделал, так это использовать предложенное решение для повторной реализации (с копированием и вставкой) этих методов в dumpObjectInfo2 () и dumpObjectTree2 () в моем собственном классе (QObjectWrapper), удалив эти средства препроцессора отладки.

Следующий код обращается к закрытому члену класса и изменяет его, используя указатель на этот класс.

#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).*/

Это «идеальная» ситуация, когда вы знаете структуру переменных экземпляра класса. Однако это неверно в случае, если у класса есть предки с ivars или есть виртуальные члены. Тем не менее, ваше решение основано на знании конкретной реализации. Так что, по сути, вы не отвечаете на исходный вопрос «Могу ли я получить доступ к закрытым членам извне класса без использования друзей?», Вы показываете, как получить доступ к памяти закрытых членов класса в конкретной реализации.

Petro Korienev 10.08.2014 17:07
class Test{
    int a;
    alignas(16) int b;
    int c;
};

Test t;

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

метод Б: грубое настроение.

int* ptr_of_member_c = reinterpret_cast<int*>(reinterpret_cast<char*>(&t) + 20);

мы используем магическое число (20), и это не всегда верно. Когда макет класса Test изменился, магическое число стало большим источником ошибок.

метод C: супер хакерское настроение. есть ненавязчивый и негрубый настрой? поскольку информация о макете класса Test скрыта компилятором, мы не можем получить информацию о смещении из уст соискателя. бывший.

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.

так что вывод может быть: Арендодатель построил весь черный ход. у вора всегда есть грубый и плохой способ взломать. у хакера есть элегантный и автоматический способ взлома.

Это похоже на напыщенную речь о других ответах, а не на ответ. У вас достаточно репутации, чтобы оставлять уточняющие комментарии к другим ответам.

Ben Voigt 11.09.2014 06:34

извините, я не усвоил правильные правила обсуждения. но вывод реален, мои мысли результаты.

thomas 11.09.2014 07:15

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

//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.....";
}

Этот ответ основан на точной концепции, продемонстрированной @ Ответ Йоханнеса / блог, поскольку это кажется единственным «законным» способом. Я преобразовал этот пример кода в удобную утилиту. Он легко совместим с C++ 03 (путем реализации std::remove_reference и замены nullptr).

Библиотека

#define CONCATE_(X, Y) X##Y
#define CONCATE(X, Y) CONCATE_(X, Y)

#define ALLOW_ACCESS(CLASS, MEMBER, ...) \
  template<typename Only, __VA_ARGS__ CLASS::*Member> \
  struct CONCATE(MEMBER, __LINE__) { friend __VA_ARGS__ CLASS::*Access(Only*) { return Member; } }; \
  template<typename> struct Only_##MEMBER; \
  template<> struct Only_##MEMBER<CLASS> { friend __VA_ARGS__ 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)

API

ALLOW_ACCESS(<class>, <member>, <type>);

Применение

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, member, int);

int main() {
  X x;
  ACCESS(x, member) = 42;
  std::cout << "proof: " << x.get_member() << std::endl;
}

Я не могу использовать это с частным членом типа enum Foo, где Foo также был в частном разделе, потому что Class::Foo не может использоваться в макросе ALLOW_ACCESS из-за того, что он частный! Какие-либо предложения?

M.M 22.02.2018 08:11

@ M.M понятия не имею, как это работает. Пробовал несколько альтернатив, но безрезультатно. Обновлю, если найду решение.

iammilind 22.02.2018 09:19

Вдохновленный @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;

  }

Что ж, со смещениями указателя это довольно просто. Сложнее всего найти смещение:

other.hpp

class Foo
{
  public:
    int pub = 35;

  private:
    int foo = 5;
    const char * secret = "private :)";
};

main.cpp

#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.

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