Воробьёвы

(-:

КОЛОНКА РЕДАКТОРА

"Добрый день $errgio и Co. Не будете ли Вы так любезны и ограничите публикацию своих выпусков единственным числом. Не то, чтобы я был в обиде, но открывать в понедельник ящик с четырьмя копиями Вашей рассылки - это перебор. Либо купите человеку публикующему на "Subscribe" путную "мышь" с не продавленными "батонами", либо... опохмеляйтесь до публикации." (C) Jerry Lee.

Дорогой Jerry Lee (кстати, ты DZ или как?)! Мы не опохмеляемся ни до, ни после публикации. Правда, один раз мы похмелялись ВМЕСТО, но это уже совсем другая история...

А вообще, я не совсем понимаю, чем плохо получить рассылку в четырех экземплярах... Это ж круто!! Один экземпляр можно прочитать. Второй на стенку в рамочку повесить. Третий другану какому-нить на день рождения подарить. С четвертым - просто напросто в толчок сходить... Разойдутся - только в путь, даже и не заметите!!

Да и потом... материал прошлого выпуска завернутый. Один из читатателей правильно догадался:

"Получил я сегодня очередной номер рассылки аж в четырех экZемплярах! Неужели такой сложный материал, что требует четырехкратного прочтения?"

ДZенское предложение :) Молодец :) Уважаю!! И следую твоему "предложению"!

Отныне номера рассылки будут дублироваться в количестве прямо пропорциональном сложности публикуемого в ней материала... Например, настоящий выпуск уйдет как минимум в 50-ти экземплярах на душу... (шутка).

На самом деле деле причина такого неприятного для всех вас дублирования вот в чем: у нас, как мы неоднократно об этом говорили, очень плохой коннект. Вследствие этого при попытке "опубликовать" рассылку через специальную форму на Горкоте, мы, не дождавшись сообщения об успешном вводе, вылетаем по таймауту. Естественно, еще раз жмем на "отправить". И еще раз (через пять минут) вылетаем по таймауту. С четвертого раза все же дожидаемся соответствующей мессаги... А получается в результате этих таймаутов вот что: подтверждение об успешном вводе мы получили только один раз, а сам "успешный ввод" был произведен аж 4 раза.

Так что смиритесь, братья... Нам это также неприятно, как и вам... Хотя ругайте :). Мы уже привыкли к кирпичам. Да и к многотонным железобетонным блокам тоже привыкать начинаем...

Пообещать вам, что больше такого не повториться - не можем. Потому что повторяться такое время от времени (хорошо, если не систематически!) обязательно будет :(.

Хотя какого черта я тут распинаюсь? Не нравиться - отписывайтесь... пока не поздно... Я ж говорил уже не раз - вы горько пожалеете о том, что подписались на нашу рассылку... не доведет она вас до добра... ой, не доведет!!

ПЕРВОЕ ПОГРУЖЕНИЕ В КОМПИЛЯЦИЮ

[1] Настало вам время, братья, тайну нервную и страшную узнать... Сегодня мы с компилятором познакомимся...

Для начала вам вот что сделать должно: скачать TASM 5.0 c нашего сайта и установить себе сие произведение человеческого гения...

Особо продвинутые юзверы спросят: а почему именно TASM, а не MASM или NASM; почему именно 5.0, а не более свежая версия? И правильно спросят!! Только вот мы им опять ничерта отвечать не будем... критикам хреновым...

Критики! Вы про бритву Оккама че-нить знаете? Если знаете, тогда ответим. Если нет, то лучше и не критикуйте - покусаем нафиг...

Короче, дZенствующие программеры! Помните нашу прогу, окошки выводящую?? Мы ее в DZEBUGE писали? Безо всяких там компиляторов?

Так вот: писать без компилятора занятие, муторное, бесполезное и, что самое главное, больно уж долгое... Но тем не менее мы это делали...

Попробую развить свою мысль:

Теоретически гвозди можно забивать и голыми руками. Но намного быстрее и безболезненнее делать это с помощью молотка. Пользоваться им, как известно, каждый дурак умеет. Чего там сложного? Взял оный в руки - и молоти: раз по гвоздю, два раза по пальцам... четвертый раз вообще не скажу по чем... но очень больно...

Как я уже неоднократно говорил, понимание приходит с опытом...

Молотки бывают разные: большие и маленькие, с длинной ручкой и с короткой ручкой, железные и деревянные, приспособленные для забивания гвоздей и приспособленные для пробивания черепов...

Да и гвоздей разнообразие еще то! И разной длины, и разной толщины, и со шляпками разной формы, из разного материала сделанные... есть даже специализированные для прибивания рук к кресту! :(.

Блин, опять меня не туда занесло... О чем это я собирался говорить? Ах да... о компиляторах...

Так вот: виды молотков и особенности их использования мы пока что оставим в покое. Для начала просто возьмем гвоздь, который уже не раз забивали голыми руками (больно было?) и забьем его при помощи молотка!

Нука вспомнили все быстренько, как мы прогу, выводящую окошки, без компилятора одними мнемониками писали!!

А теперь ее же, дуру, "под компилятор" щас перелопатим...

[2] Нижеследующий текст набираем В ТЕКСТОВОМ РЕДАКТОРЕ, желательно каком-нить досовском... хотя и нотепад тож сгодится... в общем, это ваше дело, где вы эту дрянь набивать будете...

   ;-[блок 1]--------------------------  
   .radix 16

   ;-[блок 2]--------------------------  
   CODESG segment
   assume CS:CODESG
   org 100

   ;-[блок 3]--------------------------  
   MAIN proc
     xor AL,AL
     mov BH,10
     mov CH,5
     mov CL,10
     mov DH,10
     mov DL,3E
     mov AH,6
     int 10
     call WINDOW
     call WINDOW
     call WINDOW
     call WINDOW
     int 20
   MAIN endp

   WINDOW proc
     ADD BH,10
     ADD CH,1
     ADD CL,1
     SUB DH,1
     SUB DL,1
     INT 10
     RET
   WINDOW endp

   ;-[блок 4]--------------------------  
   CODESG ends

   end MAIN

Сохраняем получившуюся дуру под именем proga_1.asm...

Далее запускаем файл tasm.exe и в качестве параметра передаем ему имя файла с исходным текстом программы...

Ну то есть командная строка у вас (в том же Windows Commander'е) вот какая должна быть: tasm proga_1.asm.

Если вы правильно набрали текст программы, то TASM должен выплюнуть вам вот что:

  Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International
  Assembling file: proga_1.asm
  Error messages: None
  Warning messages: None
  Passes: 1
  Remaining memory: 406k

Самое тут главное - это чтоб "Error messages" был "None" равен. Это значит, что ошибок в программе нет.

Поехали дальше... Если ошибок у вас действительно никаких не было, то в том же каталоге, что и proga_1.asm ищите файл proga_1.OBJ. Можете даже по F3 попробовать просмотреть его содержимое... Че-нить поняли? Ну конечно же все все поняли...

А теперь запускаем хорошую программу TLINK следующим образом: tlink /t proga_1.obj.

Обратите внимание: линкуем мы именно файл с типом OBJ, а не ASM.

Что получилось? А получился файл proga_1.COM!! И этот .COM работает!

Посмотрите на его содержимое в DZEBUG'е :). Отличется ли оно чем от той проги, что мы в выпуске 9 п. 3 делали?

Нет, не отличается!! И это правильно!

[3] А теперь несколько слов о том, почему не стоит писать программы так, как мы это делали раньше:

1. Эти дЭбильные адреса!! Пойди рассчитай на какой адрес прыгнуть нужно, с какого смещения подпрограмма начинается, с какого блок данных... В принципе, как мы уже убедились, в этом нет ничего сложного... просто занятие это очень уж муторное и неинтересное...

Как абсолютно верно заметил некто Евгений Олейников в RTFM_Helpers: "Когда я пишу программу, я не знаю точного адреса начала будущей подпрограммы"...

2. Очень сложно было в том случае, когда возникала необходимость ВСТАВИТЬ ту или иную команду в середину кода. Приходилось перебивать код по новой... по новой пересчитывать адреса... ужас, в общем! Хотя тоже ничего сложного... но все равно - неприятно...

Так вот: основная и самая главная функция "ассемблерного компилятора" - это как раз и есть "просчитывание адресов"!

Смотрите, как хорошо и приятно:

Мы готовим исходный текст в обыкновенном текстовом редакторе :). Просто набиваем строчка за строчкой :)... пофиг нам адреса (мы их вообще не видим!), пофиг нам "точки входа" в процедуру... Спокойненько вставляем какую надо команду, спокойненько удаляем... Спокойненько и без напряга!

Да вы посмотрите на блок 3 программы :). Там все те же хорошо знакомые команды :).

Особое внимание обратите на команду CALL, которая у нас, как известно, вызывает процедуру. После нее идет не привычный адрес начала процедуры, а всего лишь ее "имя собственное"! А сама процедура находится между строчками WINDOW proс (начало процедуры) и WINDOW endp (конец процедуры).

"WINDOW" - понятно. Это "имя собственное". "Proc" - потому что процедура. "Endp" - потому что конец процедуры...

Тут еще один момент... подобные "словеса" КОМАНДАМИ АССЕМБЛЕРА НЕ ЯВЛЯЮТСЯ. Они называются инече - "директивами". Это не ПРИКАЗ делать то-то и то-то, а ЦЕННОЕ УКАЗАНИЕ компилятору (не процессору!), что и как ему делать с данным куском кода...

Процедуры - вещь хорошая! Все процедуры хороши, и в большой программе их чертовски много! Это вам не языки высокого уровня, где можно длиннющие простыни кода лабать и все будет работать! ("Дельфи-компилятор не даст вам выстрелить себе в ногу, но будет так удивлен попыткой сделать это, что через некоторое время сделает это сам." (С) DZ WhiteUnicorn).

Это ASM! Тут запутаться легче простого! Исходники неимоверно длинны и сложны! Все делается ручками (хоть и с помощью компилятора), а ошибки довольно трудно отслеживаются (для не-дZенствующих это вообще занятие безнадежное)! Вот и выкручиваются низкоуровневые программеры таким вот образом: всю программу (даже в тех случаях, когда это не обоснованно!) делят на меленькие кусочки-процедурки... Напишут процедурку, протестируют ее так и сяк... если работает - за следующую берутся...

Сии "методы проектирования" мы еще с вами еще не раз рассмотрим :). Пока что знайте вот что: любую прогу можно/нужно рассматривать как КУЧУ ПРОЦЕДУР, которые все между собой повязаны...

Но есть среди этих процедур САМАЯ ГЛАВНАЯ! Это та, С КОТОРОЙ НАЧИНАЕТСЯ ВЫПОЛЕНИЕ ПРОГРАММЫ! Ее никто не вызывает. Она - босс! Она всеми командует, все гребет под себя... Описывают ее те же самые директивы, что и прочие "подчиненные процедуры"... но есть у нас еще одна директивка, которая указывает, КАКАЯ ИМЕННО процедура ИЗ ВСЕХ вроде бы "равноправных" является ГЛАВНОЙ.

Видите строчку end MAIN в конце исходного текста программы? Именно она и указывает ГЛАВНУЮ ПРОЦЕДУРУ ("MAIN" - ее имя собственное). Если бы мы написали end WINDOW, то выполнение программы у нас началось бы с первой строчки процедуры WINDOW, и ни одна строчка из MAIN выполнена не была бы...

Уф... в общем долго и упорно медитируйте...

[4] Продолжу...

Наличие многочисленных директив - это своего рода плата за то, что компилятор избавляет нас от необходимости просчитывать адреса. Как говорит дZенская программерская мудрость "любишь кататься, люби и саночки возить"...

Как и в DZEBUG'е, "в TASM'e" мы также должны четко инструктировать компилятор (дабы он в свою очередь также четко проинструктировал процессор) что у нас является кодом, а что - данными...

Посмотрите на исходник. Весь текст программы у нас хранится между директивами CODESG segment и CODESG ends. Где CODESG - это имя собственное, "segment" - потому что "CODESG" он, собственно, и есть сегмент :), и "ends" - потому что конец сегмента... (сравните с процедурными директивами).

Но тут такой вопрос:

Сегмент, он, как известно, может быть и сегментом кода, и сегментом данных, и сегментом стека... Как нам "указать", что это ИМЕННО сегмент кода??

Это нам позволяет сделать директива ASSUME :). Она "привязывает" сегмент с каким-либо "именем собственным" к определенному регистру.

Напишите assume CS:CODESG и компилятор благополучно переведет это в цепочку "шестнадцатеричных циферек" и скажет процессору, что это код.

Напишите assume DS:CODESG и компилятор безуспешно попытается перевести это в цепочку шестнадцатеричных циферек и подсунуть их процессору как данные...

То есть мало сказать компилятору, что ЭТО СЕГМЕНТ. Нужно еще и уточнить, КАКОЙ при помощи директивы ASSUME.

Далее у нас следует директива org 100. Нужна она нам для того, чтобы компилятор понял, что мы хотим получить именно COM-файл (который, как известно, помещается в сегмент памяти начиная со смещения 100). Директивка очень интересная :), о ней мы еще поговорим подробнее, когда коснемся вирмейкерства ;)...

[5] Поговорим теперь о директиве RADIX. Очень уж она мне нравится :). Истинно говорю вам: дZенская директива!

В DZEBUGE у нас все циферки дефолтом считались шестнадцатеричными. Других систем счисления он не понимал...

А вот компилятор за милую душу принимает вот что:

  mov AX,12d (десятичная)
  mov AX,0Сh (шестнадцатеричная)
  mov AX,1100b (двоичная).

Директива RADIX устанавливает систему счисления, которая будет использоваться по умолчанию. То есть число 12 в случае

  .radix 10
  ...
  mov AX,12

будет интерпретированно, как 12d. А в случае

  .radix 16
  ...
  mov AX,12

- как 12h.

Это если d, h, или b после циферек не писать. Если напишите - то RADIX пофиг... Как напишете, в такой СЧ и считаться будет...

Я к чему это... Давайте всегда RADIX 16 использовать, а не 10! Это не только настроит вас на дZенский лад (не челове-е-е-еческий, не десяти-и-ичный), но и избавит от целого ряда психоделических глюков... В общем - привыкайте считать по-компьютерному!!

[6] Ладно... с директивами более-менее разобрались, исходник приготовили, пора разбираться че там дальше происходит...

Дальше происходит так называемое "ассемблирование", т.е. перевод команд в соответствующие машинные коды. При этом просчитываются адреса меток, адреса начала подпрограмм, адреса начала/конца сегментов... и многое другое...

Причем ассемблирование происходит как минимум в два приема. Посудите сами: откуда компилятору знать, с какого адреса начнется процедура WINDOW, если не известно, какая еще простыня команд ПОСЛЕ этого CALL'а будет? В DZEBUG'е мы это "в уме на листике" считали...

TASM это тоже аналогичным образом делает :).

При первом проходе он подсчитывает, сколько какая команда занимает места, с каких адресов начинаются процедуры и т. д. и только при втором проходе подставляет в call'ы КОНКРЕТНЫЕ АДРЕСА начала этих процедур... всего лишь... ну еще и ваши "d" и "b" в машинные "h" (которые на самом деле "b") переводит... (во завернул!)...

А вообще TASM много еще чего делает... программеры народ ленивый...

В результате ассемблирования мы получаем так называемый "объектный файл".

"И что это за дрянь?" - спросте вы и правильно спросите...

А вы сравните шестнадцатеричное содержимое OBJ и COM файлов. В OBJ присутствует та же последовательность байтов, что и в OBJ. Но помимо этого и еще какая-то шестнадцатеричная ерунда присутствует: имя сассемблированного файла, версия ассемблера, "имя собственное" сегмента и т. д.

Это своего рода "служебная" информация, предназначенная для тех случаев, когда ваш исполнимый файл вы хотите собрать из нескольких. При разработке больших приложений исходный текст, как правило, хранится в нескольких файлах в каждом из них прописаны свои сегменты кода/данных/стека. А исполнимый получить нам нужно только один - с единым сегментом кода/данных/стека. Именно это TLINK и делает: завершает определение адресных ссылок и объединяет, если это требуется, несколько программных модулей в один...

И этот один у нас и является исполнимым... УРА!

[7] 4 февраля 2001 года. 19.58. Маленький уездный городок... Хреновый но зато халявный интернет...

На улице стоит дубак. В окно светит галимая луна...

Отключили отопление... Я сижу в шубе и трясущимися рукамммми стараюсь добить сей выпуск рассылки... Холод - это куда поххххуже кккирпичей...

Ну что мне вам напоследок еще сказать? Если кто не въехал "в директивы" - не отчаивайтесь. Читайте дополнительную литературу... Медитируйте...

И да пребудет с вами сила! И тепло - в душе и теле... Аминь...

На сегодня это, пожалуй, все...

Ххххолодно...

АНОНС

В следующее воскресенье... тоже что-нибудь будет... ЕСЛИ.