Программы
ВХОД
Логин:    

Пароль:  

   Запомнить меня
Вам нужно авторизоваться.
Забыли пароль? / Регистрация
Статьи


   Статьи
   Soft
   Программирование
   Как работает компилятор? Часть первая.

Как работает компилятор? Часть первая.

Добавлено: 25.05.2012

Прочитано: 3020

Работа компилятора в картинках. Часть первая

Теперь мы попробуем заинтересовать вас еще больше, показав примеры, как реальные компиляторы «старых лет» преобразуют привычные действия. Вы можете ничего не понимать в машинном коде, поэтому будем комментировать. С другой стороны, наивно думать, что какие-то там программы, пусть даже и компиляторы, способы использовать сотни видов команд, которые поддерживает процессор – это не так. Практически все из них до сих пор не только создают программы в расчете на Intel 286/386, но даже из того набора берется лишь 5-10% типов команд! Основные из них, конечно:

  • Передача данных между ячейками (регистры и память) – MOV
  • Сравнить данные – CMP
  • Обнуление регистра – XOR. Можно было бы MOV регистр, 0, но XOR регистр, регистр занимает меньше памяти.
  • Переходы по условию – JNE
  • Безусловные переходы – JMP
  • Загрузка в стек – PUSH
  • Выгрузка из стека – POP
  • Автоинкремент и автодекремент регистра – INC/DEC
  • Вызов подпрограммы (ближний или дальний) – CALL

Вот, пожалуй, и все. Сам компилятор может использовать больший набор команд, чем генерирует для программы. Давайте начнем с любимого TURBO Pascal 7.1.

Var
J: integer;
Begin
For j:=1 to 4 do writeln(‘Строка’);
End.

Из программы даже неспециалист увидит, что программа 4 раза выведет на экран слово «Строка». Как это будет в коде (2208 байтов), лучше смотреть через программу HIEW. Чтобы получить такой же экран, нужно написать команду hiew <имя_программы.exe>, затем по F4 выбрать режим Decode, нажать F8 и F5 (указатель встанет на начало нашей программы). Хотите верьте, хотите нет, но это ВСЯ программа, которую мы написали, остальное – паскалевские настройки, стандартные библиотеки и т.д.

как работает компилятор

Если посмотреть внимательно, то можно увидеть вызовы типа call 0005:xxxx – это как раз вызов библиотек. Первое число может менять, но только не xxxx, которое является смещением вызова относительно начала библиотеки. И так во всех программах. Соответственно:

0000 – инициализация программы (туда лучше не смотреть, там все сложно);
02CD – тоже какая-то подготовка (наша программа еще не началась);
0670, 05DD, 0291 – идут часто вместе и обозначают вызов writeln;
0116 – завершение программы.

Наша единственная переменная хранится где-то в секции данных, в ячейке со смещением 52, причем видно, что программа к ней обращается как к слову ([w]ord ptr), то есть это либо тип integer, либо word – другого не дано. Если бы мы указали для j тип byte, то обращение бы шло к половинке ячейки – [b]yte ptr.

Команда mov w, [52], 1 откровенно навевает на мысль, что это начальное заполнение ячейки j. В самом деле, если бы хотели описать все действия в виде алгоритма, то придумали бы следующее:

  • Записать в j единицу (mov w, [52], 1)
  • Перейти к шагу 4. (jmp 72, т.е. безусловный переход на шаг 4)
  • Прибавить к j единицу (счетчик работает). (inc [52])
  • Вывести на экран строку. (с адреса от 72 до 8D включительно)
  • Сравнить j с 4. (cmp w, [52], 4)
  • Если меньше, то переход на шаг 3. (jne 6E, т.е. на шаг 3)
  • Завершить программу. (с адреса 95 по 9С)

Обратите внимание, что по адресу 8E указано 16E, а мы написали 6E – это ошибка программы Hiew, которая не сумела определить переход назад. Бывает…

Как видите, программа реализована в точности по алгоритму. С ней можно поиграться, заменив в режиме редактора, к примеру, константу 1 или 4 на другие числа – программа будет выводить строку не 4 раза, а сколько скажете. Это называется «патчинг» – замена алгоритма в готовой программе без изменения исходной. Таким приемом часто пользуются хакеры для снятия защиты. В самом деле, мы можем и не вызывать процедуру печати, забив адресное пространство с 72 до 8D однобайтовой командой nop (ничего не делающей), и тогда writeln не будет вызываться вообще. Или вставим в это место совсем другой код, подходящий по размеру (но только не вирусы, они уже надоели!).

Главное, что вы сейчас должны понять, состоит в следующем:

Компилятор всегда работает по одному принципу, использует готовые конструкции. То есть для for переменная:=значение1 to значение2 всегда будет создаваться конструкция:

1: Присвоить переменной значение1
2: Перейти к шагу 4
3: Увеличить переменную на единицу
4: Что-то сделать (не зря же работает цикл?)
5: Сравнить переменную со значением2
6: Если они не равны, то переход на шаг 3

Весь интерес, конечно, представляет блок в шаге 4, где может быть очень много команд. Но принцип ясен. Кстати, как вы знаете, в Бейсиках эта команда имеет дополнительный параметр STEP x, которая определяет шаг итерации в цикле. Для Паскаля решили остановиться на единице, но можно представить, что значение шага хранилось бы в ячейке 54, и команда по адресу 6E выглядела бы иначе: add w, [52], bx, где в bx переписывалось бы значение из ячейки 54. А что делать, если в этом процессоре НЕЛЬЗЯ передавать данные из ячейки в ячейку напрямую – только через регистр? Зато компилятор бы усложнился, ведь пришлось бы ставить внутренний контроль за итерациями! Подумайте:

FOR J:=1 to 10 STEP 3 – сколько раз выполнится цикл? И кто будет следить за этим? А компилятор должен следить: и за тем, чтобы стек не кончился в результате рекурсий, и чтобы значение не вышло за рамки массива, и чтобы не было деления на ноль… Много чего.

Кстати, указанный выше шаблон почти так же работает для циклов repeat … until, while … do. Интересно, как располагают переменные в массиве? А что будет, если по адресу 96 не обнулить регистр ax? Об этом и другом читайте в следующей части.



обновить программы бесплатно

<<  Что такое компилятор? Работа компилятора в картинках. Часть вторая  >>


Добавить Комментарий

Скачать программу для проверки на ошибки
Скачать программу автоматического обновления программ
Статьи
Новые Программы
Новые статьи
Популярные Программы
Самые читаемые статьи
Copyright © Дай Прогу 2011 Контакты ¤ Статистика