Статьи по Assembler

       

Зачем он нужен, этот ассемблер?


Читайте также:

  • обсуждение этой статьи c MemoBreaker'ом
  • дополнение Геннадия Майко

Вопрос о том, имеет ли смысл заниматься разработкой приложений для Windows на ассемблере, довольно часто возникает в разных дискуссиях, форумах и конференциях Usenet. Как правило, обсуждающие высказывают при этом точки зрения, основанные на личных впечатлениях, которые не могут убедить никого, кроме их самих. Спектр мнений лежит в интервале от "в эпоху объектно-ориентированного программирования, на котором зиждется вся Windows, это полная дурость" и до "программировать на ассемблере под Windows легче, чем на C++".

Мы не тешим себя надеждой дать на этот вопрос однозначный ответ. Более того, мы уверены, что эта статья вообще ничего не изменит во взглядах ее читателей. Те из них, кто считал ассемблер подходящим прикладным языком, так и будут считать дальше. А те, кто считал первых чудаками, только укрепятся в собственном мнении.

Потому что этот вопрос не имеет объективного, независимого от каждого конкретного человека, ответа. Ответ на него лежит в области психологии, мироощущения.

Скажите пожалуйста, почему объектно-ориентированное программирование, имея уже довольно долгую историю, так и не заняло на сегодняшний момент подобающего ему первого и единственного места? Оно продвигается в жизнь программистским авангардом, в первую очередь Microsoft, и благодаря этим усилиям явочным порядком все-таки постепенно завоевывает мир. Некоторые из его элементов, как, например, инкапсуляция, стали очевидной нормой. Но 90% программистов, честно перебрав свой повседневный инструментарий, вряд ли найдут в нем, например, перегрузку операций, наследование, абстрактные классы, несмотря на то, что этим темам посвящена большая часть любого учебника по C++. Как следствие - перспективные технологии вроде COM встречают брюзжание и неприятие со стороны широких масс. Один руководитель серьезного коллектива разработчиков ответил на вопрос, почему его люди до сих пор пишут в стиле Фортрана, примерно так: "Некогда фигней заниматься, дел полно" (при том, что на старте в этот момент находился крупный проект, который по своим характеристикам как нельзя лучше ложился на COM).


Все дело в том, что далеко не все люди смотрят на мир объектно-ориентированно. Как не все мы одинаково годимся в поэты, диспетчеры АЭС, киллеры и мотогонщики, так не всем нам дан Богом дар абстракции. Проектируя приложение и принимая принципиальные решения, или выбирая, как построить конкретный фрагмент программы, многие видят своим мысленным взором не мир людей с характерными для каждого цветом глаз, размером кулака и профессией, а все ту же классическую фоннеймановскую архитектуру ЭВМ: АЛУ, ЗУ и прочее. Возможно, такое положение вещей определилось исторически, и когда вымрет пара-тройка поколений выпускников ФПМ и на Земле не останется ни одного кейса на винтиках, детская болезнь программирования будет навсегда забыта.

Рискнем, однако, высказать предположение, что не все так радужно. Причина, почему многие (как бы не большинство) программисты с трудом принимают объектно-ориентированную парадигму кроется во-первых, в личностных характеристиках, данных человеку от рождения, и, во-вторых, в недостатках самой парадигмы.

Что касается личностных характеристик, то попробуйте воспользоваться тестом. Шутка, конечно, но, как положено, с долей шутки.

Недостатки объектно-ориентированной парадигмы, как правило, не обсуждаются. В любом учебнике много говорится о том, как она хороша, и почти ничего - о ее проблемах. А они, тем не менее, существуют.

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

Например, популярный у авторов учебников пример со студенческой группой. Вершиной иерархии в этом примере является класс Student, имеющий базовый набор свойств и методов. Нижележащие уровни иерархии составляют наследующие классы - староста группы, участник научного общества, член сборной университета по бейсболу и прочее. Эта иерархия поставлена с ног на голову относительно того, что мы наблюдаем в реальной жизни. Иерархии реальной жизни основаны не на общих наборах свойств у их членов, а наоборот, на различиях. Староста группы, несомненно, выше в реальной иерархии, чем рядовой студент. Миллионер выше представителя среднего класса, хоть у обоих и по две ноги и две руки.





Еще больше отличается наследование ООП от одноименного явления живой природы. Здесь одинаковые названия порождают просто невообразимую путаницу. Отношения детей и родителей в живой природе вообще не иерархичны в том смысле, который подразумевает наследование ООП. Дети - это не копии своих родителей, имеющие кое-какие дополнительные свойства, и кое-какие видоизмененные родительские. Слишком часто дети вообще ничем не напоминают своих родителей.

В итоге наследование, будучи объективно чрезвычайно полезным инструментом, из-за выбора популяризаторами ошибочных аналогий с трудом добирается до мозгов загнанных текучкой разработчиков.

Современное программирование имеет гигантский набор разнообразных инструментов, более-менее универсальных и специализированных, мощных и простых, технологичных и навороченных, для дилетантов и для профессионалов. И каждый из них, по большому счету, является всего лишь набором приспособлений, помогающих решению тех или иных задач. Облегчая решение одних задач, он усложняет решение других.

В массовом сознании существует лишь один по-настоящему универсальный инструмент - C/C++. Всякий программист, работая с другими языками, постоянно имеет ввиду существование этого самого профессионального из всех профессиональных инструментов. То есть как бы ощущает себя не до конца профессионалом.

То есть выбор не так уж велик. Либо ты владеешь C/C++, и ты абсолютный профессионал, либо нет - и ты как бы немножко ненастоящий программист.

А уж осознав это, и даже, может быть, став мастерами C/C++, люди вспоминают об ассемблере. А еще чаще о нем вспоминают новички, желающие стать профессионалами и только-только выбирающие свой путь. Вот почему так часто возникают вопросы: имеет ли смысл программировать на ассемблере.

Есть и еще одно обстоятельство, стимулирующее интерес к ассемблеру. Под Windows на нем действительно программировать много легче, чем под DOS. Самое главное, благодаря flat-модели ушла в прошлое возня с сегментами памяти и сегментными регистрами. А сервис API делает теперь многое из той рутины, на которую уходила раньше большая часть рабочего времени программиста.



В этой статье мы избрали вот какой способ ответа на заглавный вопрос. Возьмем какую-нибудь задачку, достаточно ограниченную, чтобы можно было ее анализировать, но в то же время достаточно серьезную, чтобы можно было наблюдать тенденции. И напишем ее два раза - на ассемблере и на C++. При этом не станем применять никаких специфических для того и для другого языка приемов, чтобы сохранить сравнимость результатов. И посмотрим, какие выводы из этого можно сделать.

Итак, приложение называется MyCall и представляет собой простейший пользовательский интерфейс для Remote Access Service, то есть звонилку. Реализовано на C++ и на ассемблере. Имеются исходные тексты:

Вариант на C++:

  • main.cpp


  • main.h


  • Вариант на ассемблере:

  • main.asm


  • main.inc


  • @struct.inc


  • windows.inc


  • Общие для обоих вариантов:

  • mycall.rc


  • Инструкция программиста


  • Начнем сравнение с трудозатрат программиста. Непосредственно сравнить время разработки не представляется возможным, так как порядок разработки был такой: сначала была написана реализация на C++, а затем, пользуясь уже отработанными решениями - на ассемблере. Можно отметить только субъективное ощущение, что на ассемблере приложение писалось бы несколько дольше, процентов на 20.

    Зато можно сравнить объективные показатели.

    Объем исходного текста на C++ - 14 Кбайт, на ассемблере - 17,5 Кбайта. Во втором случае пришлось сделать почти на 20% больше ударов по клавишам. Туннельного синдрома, конечно, не заполучил, но ведь и приложеньице-то масенькое.

    Число строк исходного текста на C++ - 384, на ассемблере - 693. Почти в два раза больше приходится скроллить листинг вверх-вниз.

    Кроме того, для реализации на ассемблере пришлось тратить время на составление файла системных заголовков windows.inc.

    В целом можно сделать вывод, что общие трудозатраты на разработку небольшого приложения в хаотическом стиле на ассемблере на 20-30% больше, чем на C/C++. Что касается больших проектов, то все зависит от того, насколько правильно будет организована работа и насколько принимаемые технические решения будут соответствовать задаче. То, что для C++ и для ассемблера эти решения будут разными - очевидно, поэтому заранее трудно сказать, какие из них ускорят разработку, а какие замедлят. Можно ведь и со всей мощью ООП утонуть в соблюдении формальных правил стиля, диктуемого языком, а можно и в изначально хаотическом языке найти такие приемы, которые дадут многократный прирост производительности труда. Тем более что в среде Windows объектно-ориентированное программирование становится вполне доступным ассемблеру (например, COM. Недавно в одной из конференций прозвучало мнение, что с COM работать на ассемблере проще, чем на C++. Спорно, но поспорить есть о чем.). Но a priori следует ожидать, что разработка проекта на C++ будет несколько менее трудоемка, чем на ассемблере.



    Теперь сравним результаты разработки, то есть готовые приложения. Это гораздо интереснее. Допустим, вы shareware-программист. Тогда вы должны знать, что сегодня почти невозможно придумать что-нибудь новенькое, аналогов чему не нашлось бы на бесчисленных download-серверах. Чаще всего разработанная вами программа попадает в категорию, где уже лежит десяток-другой ее близких и дальних родственников. И еще вы должны знать, что, каким бы завлекательным и подробным ни было описание приложения, первый взгляд, который бросает пользователь, играющий роль буриданова осла(по Стругацким - барана), падает на графу "размер".

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

    Размер исполняемого модуля mycall.exe в реализации на C++ - 8704 байта. На ассемблере - 8704 байта.

    Разочарование! Никакого выигрыша!

    Стоп. Давайте сначала вспомним, что речь идет о PE-файле. И имеет смысл заглянуть внутрь него: так ли уж все одинаково в том и другом случае, как кажется на первый взгляд.

    имя
    секции
    назначение
    секции
    размер в файле размер в памяти
    C++(speed) C++(size) asm C++(speed) C++(size) asm
    .text код 1000h (4096) 0e00h (3584) 0e00h (3584) 0f22h (3874) 0c84h (3204) 0c54h (3156)
    .rdata константы нет нет 0400h (1024) нет нет 0315h (789)
    .data переменные 0400h (1024) 0400h (1024) 0 042ch (1068) 042ch (1068) 05c1h (1473)
    .idata данные импорта 0400h (1024) 0400h (1024) 0400h (1024) 03eeh (1006) 03eeh (1006) 03eah (1002)
    .rsrc ресурсы 0800h (2048) 0800h (2048) 0800h (2048) 0648h (1608) 0648h (1608) 0648h (1608)
    В этой таблице перечислены все секции PE-файла приложения в каждой из трех реализаций: на C++ с оптимизацией по времени выполнения, на C++ с оптимизацией по размеру и на ассемблере.

    Кстати, размер исполняемого модуля в реализации C++ с оптимизацией по времени выполнения несколько больше - 9216 байт. Но при программировании приложений для Windows такую оптимизацию использовать обычно нет смысла, так как многозадачная архитектура и механизм сообщений отнимают куда больше временного ресурса, чем может дать выигрыш от нее.



    Вот что видно из этой таблицы:


    • размер кода в реализации на ассемблере немного меньше, чем в реализации на C++(size) и существенно, на 20%, меньше, чем в реализации C++(speed)
    • различия в размере всех остальных секций не принципиальны


    А равенство размеров исполняемых модулей для C++(size) и ассемблера объясняется тем, что размеры секций в PE-файле выравниваются на ближайшую большую величину, по умолчанию - 200h байт.

    Итак, пора подвести итог вышесказанному:


    • Разработка приложения на ассемблере несколько более трудоемка, чем на C++. Однако нельзя сказать, что это различие настолько велико, что C++ имеет безусловное преимущество
    • Размер получаемого приложения на ассемблере сравним с размером приложения на C++ при условии, что приложение на C++ компилировалось с оптимизацией по размеру кода.
    • Размер получаемого приложения на ассемблере примерно на 20% меньше, чем размер приложения на C++, компилированного с оптимизацией по скорости выполнения. Однако этот выигрыш получается только за счет кода.


    Решать, стоит ли заниматься разработкой приложений на ассемблере, должен каждый для себя сам. Существенных объективных преимуществ в разработке приложений для Windows перед C++ ассемблер не дает.


    Содержание раздела