пользовательский тип данных / операции с набором команд процессора - PullRequest
0 голосов
/ 02 октября 2010

В любой среде программирования, независимо от того, какой тип данных я собираюсь выбрать, ЦПУ будет выполнять только арифметические операции (сложение / логические операции).

Как происходит этот переход (от определяемого пользователем типа данных / операций к набору команд ЦП) и какова роль компилятора, интерпретатора, ассемблера и компоновщика в этом жизненном цикле

Кроме того, как OOPS обрабатывает это отображение, поскольку в худшем случае в основном все объекты являются объектами в OOPS (я имею в виду язык Java) ..

Ответы [ 2 ]

3 голосов
/ 02 октября 2010

Исходный код Java -> преобразование собственного кода на самом деле происходит в два отдельных этапа: преобразование исходного кода в байт-код во время компиляции (это делает javac) и преобразование из байт-кода в собственные инструкции ЦП во время выполнения ( что java делает).

Когда исходный код "компилируется", поля и методы уплотняются в записи в таблице символов. Вы говорите «System.out.println ()», и javac превращает его в нечто вроде «получить статическое поле, на которое ссылается символ # 2004, и вызывать метод, на который ссылается символ # 300» (где # 2004 может быть "System.out" и # 300 может быть "void java.io.PrintStream.println ()"). (Обратите внимание, я слишком упрощен - символы выглядят совсем не так, и они немного больше разделены. Но они содержат такую ​​информацию.)

Во время выполнения JVM просматривает эти символы, загружает классы, на которые они ссылаются, и запускает (или генерирует, если это JITting) нативные инструкции, необходимые для поиска и выполнения метода. В Java нет настоящего «компоновщика»; Все ссылки выполняются во время выполнения, основываясь на ссылочных классах. Это очень похоже на работу DLL в Windows.

JIT - самая близкая вещь к "ассемблеру". Он принимает байт-код и генерирует эквивалентный собственный код на лету. Байт-код не в удобочитаемой форме, поэтому я бы не стал считать перевод «сборкой».

...

В таких языках, как C и C ++ (не C ++ / CLI), история совсем другая. Весь перевод (и хорошее связывание) происходит во время компиляции. Доступ к элементам struct преобразуется во что-то вроде «дайте мне 4 байта int от начала этой конкретной группы байтов». Там нет гибкости там; если макет структуры изменяется, как правило, все приложение должно быть перекомпилировано.

1 голос
/ 02 октября 2010

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

Соотношение между этим и машинным кодом, используемым процессором, будет относительно ясным (хотя на самом деле мы вполне можем оптимизировать и дальше).

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

Теперь предположим, что мы хотим переместить это в точку, где мы можем получить что-то вроде:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...