#опытным
Продолжаем исследовать грязь и разврат. Предыдущий пост тут.
Указатели, блин, сложная тема. А для задачи доступа к приватным полям - особенно. Для объектов классов с несколькими полями нужно считать много деталей: учитывать выравнивание полей в объекте и на этой базе высчитывать сдвиг типизированного указателя. Возможно даже несколько кастов понадобиться.
Программисты - люди ленивые, поэтому давайте как-то упрощать.
Допустим, у меня есть класс Target с множеством полей. А что, если где-то будет структурка Hack, идентичная по расположению полей? Может быть как-то можно интерпретировать данные Target, как данные Hack?
Конечно можно. В плюсах можно ВСЕ.
Конкретно интерпретировать одни данные, как другие, позволяет делать union. Он хранит один объект любого типа из списка, перечисленного внутри union. Но объединение не знает, объект какого конкретного типа он хранит. Это возлагается на плечи программиста, а union позволяет "достать" этот объект через любой тип.
struct Target {
private:
int x = 42;
double y = 3.14;
};
union HackUnion {
HackUnion() {}
Target target;
struct {
int stolen_x;
double stolen_y;
} hacker;
};
int main() {
HackUnion u;
u.target = Target();
std::cout << "Stolen x: " << u.hacker.stolen_x << std::endl;
std::cout << "Stolen y: " << u.hacker.stolen_y << std::endl;
}
// OUTPUT:
// Stolen x: 42
// Stolen y: 3.14Внутри union определяет структуру с точно таким же layout'ом, как у Target, только члены у нее публичные. И интерпретируем данные Target как набор полей этой структуры. Так мы можем достать stolen_x и stolen_y.
И это даже не UB, а вполне валидное поведение! Стандарт гарантирует, что обе структуры layout-compatible и имеют общую начальную последовательность полей. И в этом случае union может их читать без угроз со стороны библии С++.
Еcли же классы будут не такие простые, даже просто с полями с разными доступами, то стандарт уже гарантировать не будет. Как мы помним, в общем случае доступ к неактивному члену юниона - ub.
Кстати, если уж мы хотим "интерпретировать" один объект как другой, можно использовать приведение типов реинтерпретацией aka reinterpret_cast:
struct Target {
private:
int x = 42;
double y = 3.14;
};
struct Hacker {
int stolen_x;
double stolen_y;
};
Target target;
Hacker* hacker = reinterpret_cast<Hacker*>(&target);
std::cout << "Stolen x: " << hacker->stolen_x << std::endl;
std::cout << "Stolen y: " << hacker->stolen_y << std::endl;Тут уже без сомнения UB, потому что нарушаем strict aliasing, но это не мешает программе успешно отрабатывать.
Пс: Получать доступ к приватным полям вне класса - плохо! Мы с вами это делаем для понимания механик языка, а не для вооружения здешних обителей оружием массового закакивания кода.
#cppcore #badpractice