Привет!
В процессе написания очередного файлового упаковщика, задался вопросом: «Каким же образом происходит деление на частные, защищенные и публичные объекты в классах?». Как оказалось, никакого деления нет — это чистая абстракция.
Ниже описано строение и функционирование классов на языке C++, а так же способы обходов запретов частных полей доступа.
Классы
И так, что же такое класс. Классы – это некая абстракция, представляющая собой совокупность атрибутов и методов класса или, как говорит нам Интернет:
Класс — разновидность абстрактного типа данных в объектно-ориентированном программировании, характеризуемый способом своего построения.
Класс, на самом деле, – это простая структура, не хранящая в себе никаких методов, полей доступа и конструкторов – вот здесь и проявляется, та самая абстракция.
Рассмотрим некий класс:
class _Class {
private:
int Value;
public:
_Class() {
}
};
Внутри данный класс представляет собой структуру данных, которая не делится, ни на частные, ни на публичные поля доступа, а структура, в свою очередь, является «массивом» элементов разной величины. Т.е. ключевое слово struct просто говорит компилятору о том, что необходимо зарезервировать N байт памяти.
struct _Class {
int Value;
};
Значит, что адрес объекта класса или структуры – есть адрес первого атрибута. Получается, что для получения значения любого атрибута класса, необходимо иметь его адрес, который с легкость можно получить сдвигом указателя объекта класса на N >= 0 байт.
Например, поменяем значение частного атрибута класса _Class:
class _Class {
private:
int Value;
public:
_Class() {
}
};
_Class Object;
__asm {
lea eax, Object
mov ebx, 123
mov [eax + 0], ebx
}
lea eax, Object
– создаем указатель на объект класса и помещаем в регистр EAX.
mov [eax + 0], ebx
– помещаем в атрибут Value, значение регистра EBX = 123. (0 – байтовый сдвиг, соответственно для второго элемента он будет равен 4 т.к. sizeof(Value) = 4).
Вот таким вот, достаточно простым и понятным способом можно получать и менять значения всех атрибутов, не зависимо от принадлежности к полю доступа.
Получается, что все «запреты» в языке C++ происходят исключительно на уровне компилятора. Аналогичным образом, используя Inline Assembler, можно менять значения константных переменных, например:
const int Value = 7;
__asm {
mov ebx, 123
mov Value, ebx
}
Методы
Но как же тогда устроены методы класса, если структура не хранит в себе указатели на функции?
Функции в классах работаю почти так же как и обычные функции. Именуются методы классов следующим образом _Class::_Class. Но основное отличие, разумеется, не в этом – это просто имя, а в 2 ключевых строках дизассемблированного кода.
1. Создается указатель на объект класса и помещается в регистр ECX
2. При вызове метода класса регистр ECX копируется в локальный указатель, именуемый this.
Пример полноценной реализации вызова метода класса на языке C++:
class _Class {
private:
int Value;
public:
_Class() {
}
public:
void Function() {
this->Value = 7;
}
};
_Class Object;
Object.Function();
А вот как это выглядит на самом деле:
struct _Class {
int Value;
};
void _Class::_Class() {
_Class* this;
__asm {
mov this, ecx
}
}
void _Class::Function() {
_Class* this;
__asm {
mov this, ecx
}
this->Value = 7;
}
_Class Object;
__asm {
lea ecx, Object
call _Class::Function
}
И о чем нам это говорит – независимо от поля, метод класса – это просто функция, которая при запуске инициализирует указатель на наш элемент, а класс — это просто структура с публичными атрибутами. Получается, что даже частные методы вызываются с помощью Inline Assembler, без каких либо запретов.
Вот и все, что я хотел рассказать. Спасибо за внимание!
Автор: DmitrySavonin