Рассмотрим отправную точку языка, который имеет только целые числа и числа с плавающей запятой различных размеров, и тип, указывающий на память, который позволяет нам указатели на эти типы.
Соотношение между этим и машинным кодом, используемым процессором, будет относительно ясным (хотя на самом деле мы вполне можем оптимизировать и дальше).
Символы, которые мы можем добавить, сохраняя кодовые точки в некоторой кодировке, и строки, которые мы строим как массивы таких символов.
Теперь предположим, что мы хотим переместить это в точку, где мы можем получить что-то вроде:
class User
{
int _id;
char* _username;
public User(int id, char* username)
{
_id = id;
_username = username;
}
public virtaul bool IsDefaultUser()
{
return _id == 0;
}
}
Первое, что нам нужно добавить к нашему языку, - это некая конструкция struct / class, которая содержит члены. Тогда мы можем иметь, насколько:
class User
{
int _id;
char* _username;
}
Наш процесс компиляции знает, что это означает сохранение целого числа, за которым следует указатель на массив символов. Поэтому он знает, что доступ к _id означает доступ к целому числу по адресу начала структуры, а доступ к _username означает доступ к указателю на char с заданным смещением от адреса начала структуры.
Учитывая это, конструктор может существовать как функция, которая выполняет что-то вроде:
_ctor_User*(int id, char* username)
{
User* toMake = ObtainMemoryForUser();
toMake._id = id;
toMake._username = ObtainMemoryAndCopyString(username);
return toMake;
}
Получение памяти и ее очистка, когда это уместно, затруднительно, взгляните на раздел в K & R о том, как использовать указатели на структуры и как malloc ищет один из способов сделать это.
С этого момента мы также можем реализовать IsDefaultUser что-то вроде:
bool _impl_IsDefaultUser(*User this)
{
return this._id == 0
}
Это не может быть отменено, хотя. Чтобы разрешить переопределение, мы меняем пользователя на:
class User
{
UserVTable* _vTable;
int _id;
char* _username;
}
Затем _vTable указывает на таблицу указателей на функции, которая в данном случае содержит одну запись, которая является указателем на функцию выше. Затем вызов виртуального члена становится вопросом просмотра правильного смещения в этой таблице и вызова соответствующей найденной функции. Производный класс будет иметь другую _vTable, которая будет такой же, за исключением наличия разных указателей функций для тех методов, которые переопределяются.
Это очень много, и не единственная возможность в каждом случае (например, v-таблицы не единственный способ реализации переопределяемых методов), но показывает, как мы можем создать объектно-ориентированный язык, который может быть скомпилировано для более примитивных операций над более примитивными типами данных.
В нем также скрывается возможность сделать что-то наподобие того, как C # компилируется в IL, который затем, в свою очередь, компилируется в машинный код, так что между языком OO и машинным кодом, который будет фактически выполняться, есть два шага.