Содержание

Все мои статьи: Статьи Игоря Романова

Наброски книги "Программирование для школьников"

Disclaimer: я вас предупреждал, что это не книга - это наброски.
Материал для детей изрядного возраста (начиная примерно с 8 класса).

Где применяются компьютеры

Да везде.
* В быту: сейчас почти любой бытовой прибор может быть автоматизирован. Нас уже давно не удивляют автоматические стиральные и посудомоечные машины, но теперь можно обнаружить встроенный микроконтроллер хоть в чайнике. Набирают популярность системы «умный дом». Сюда же добавим электронные счетчики электроэнергии, хотя они есть не у всех (а у кого-то уже и воду считает микропроцессор!)
* В сфере досуга и развлечений: компьютерные игры, а также всевозможные самодействующие игрушки, модели, макеты; цифровая фотография, аудио- и видеозапись, цифровое рисование; устройства типа сигвеев, гироскутеров, моноколес, если конечно не считать их транспортными средствами. Сюда же добавим и собственно интернет.
* В различных учреждениях: системы контроля доступа людей в здания и помещения; видеонаблюдение, пожарная сигнализация; лифты.
* В бизнесе, торговле и экономике: системы складского учета и управления; подготовка, хранение, передача и анализ различных документов, в т. ч. отчетных (электронная бухгалтерия); анализ, прогнозирование и моделирование экономических процессов; финансовые операции «онлайн»; безналичные расчетно-платежные системы; компьютеризированные кассовые аппараты, банкоматы и платежные терминалы; системы электронной подписи и близкие к ним (системы «банк-клиент»).
* В науке и инженерной деятельности.
* В медицине сейчас используется огромное разнообразие специализированных компьютеров и компьютеризированных приборов, как собственно лечебных, так и исследовательско-диагностических.
* В промышленности: для обеспечения оптимальных и безопасных режимов работы различного оборудования; станки с числовым программным управлением (ЧПУ) и промышленные роботы; системы управления производством (АСУ).
* В книгоиздательском и книгопечатном деле.
* В связи: любой современный телефонный аппарат или радиостанция, телевизор или радиоприемник представляет собой не что иное как специализированный компьютер. К этому добавим аппаратуру телефонных станций, дата-центров, станции распределения телевизионных сигналов (как земные, так и спутниковые), системы управления движением почтовых посылок.
* На транспорте: обеспечение нужных режимов работы двигателей и иных механизмов транспортных средств; навигация; обеспечение безопасности движения транспортных средств по дорогам; автоматизация погрузочно-разгрузочных работ, составление и распространение расписаний, маршрутизация грузопотоков, продажа билетов, в том числе через интернет с оплатой банковскими картами.
* В военном деле: сбор, передача и отображение информации о расположении сил противника и своих; анализ, моделирование и планирование боевых операций; наведение артиллерийских орудий и других видов оружия; постановка радиопомех противнику и преодоление его помех («радиоэлектронная борьба»); системы опознания «свой-чужой» для кораблей и самолетов; тренажеры для обучения личного состава.
* В государственных делах: обеспечение дипломатической и другой важной переписки (шифровальные машины и шифрсистемы); экспертно-криминалистические приборы и программы.
Чтобы все это проиллюстрировать, давайте прикинем: сколько компьютеров у каждого из вас?
Сколько у вас, я не знаю, поэтому расскажу, что у меня.
* Собственно компьютер.
* Роутер: это специализированный компьютер с двумя сетевыми картами, решающий одну несложную задачу: маршрутизацию (шлюзование) данных между моей внутриквартирной локальной сетью и сетью интернет-провайдера.
* Телевизор: это тоже специализированный компьютер со встроенным экраном, а его функции включают регулирование яркости, громкости и т. п. и выбор канала по командам с пульта ДУ, а также воспроизведение фильмов, фотографий и музыки с флэшки. Это довольно старый телевизор, более новые гораздо «умнее».
* Телевизионная приставка наземного цифрового телевидения (DVB-T2), которая кстати тоже воспроизводит кино.
* Сотовый телефон: в нем имеется процессор с функциями управления режимами связи, но этот процессор «умеет» делать много такого, что к сотовой связи вроде бы не имеет отношения, например запись и воспроизведение звука, будильник, калькулятор и много чего еще.
* Банковская карта. Да, не удивляйтесь: в ней вмонтирован крошечный процессор с памятью и файловой системой, хранящей информацию о карте и ее владельце.
* RFID-ключ («ключ-таблетка»): после банковской карты, надеюсь, упоминание этого ключа в этом списке вас не удивит. Ну а если упоминать ключ, значит не забыть и замок, который этим ключом открывается.
* Стиральная машина.
* Мультиварка.
* В автомобиле лично у меня три микропроцессора: один - в системе обеспечения работы двигателя (инжектор и зажигание), второй в радиоприемнике и третий - в антибуксовочной системе (АБС). В вашем автомобиле, если он дизельный, инжектора и зажигания может и не быть, зато у вас возможно есть навигашка, а это даже не один, а целый комплекс микропроцессоров, один из которых занимается только вычислением координат, а другой отображает на экране карту и выдает голосовые подсказки типа «через 100 метров поверните направо». Вообще в современном автомобиле микропроцессор можно найти чуть ли не в каждом стеклоподъемнике.
* Фотоаппарат.
* Электронная книжка («читалка»).
Как видим, немало (а всего 20 лет назад я ни о чем таком и не мечтал!)
Теперь обратим внимание на то, чего лично у меня нет, но может быть у вас:
* Кондиционер. Если он имеет пульт ДУ, то можно не сомневаться, что сигналы пульта принимает и анализирует маленький процессор.
* Всевозможные цифровые часы, хоть наручные, хоть настенные: если они не очень старые, то без микропроцессора там точно не обойтись.
* Возможно, у кого-то в вашей семье имеется автоматический измеритель артериального давления (тонометр) или анализатор сахара в крови…
* Квадракоптер: такие «игрушки», появившиеся лет 10 назад, без микропроцессоров совершенно немыслимы.
* Игровая приставка.

Какие были компьютеры раньше и какие бывают сейчас

Когда появился первый компьютер?
На этот вопрос нет однозначного ответа. Во-первых, чем глубже интересуешься техникой, тем больше ощущаешь, что мы плохо знаем даже историю 30-40-летней давности, не говоря уж про более давние времена. Во-вторых, «первый компьютер» не возник на пустом месте - он вырос из каких-то более старых устройств. Что из них можно считать компьютером, а что нет?
Эта тема выделена в отдельную статью: Нетрадиционный взгляд на историю вычислительной техники

Современные компьютеры и подобные им устройства

«Настоящий» компьютер характеризуется тем, что «общается» с человеком, получает от него команды и исходные данные для исполнения этих команд, а результаты выдает опять-таки человеку. Соответственно компьютер имеет устройства для «общения» с человеком - HID - Human Interface Devices - буквально: устройства сопряжения с человеком. Это экран (монитор), клавиатура, различные манипуляторы типа мыши или джойстика, принтер…
«Большой компьютер» - выражение, которое встречается в речи специалистов-компьютерщиков, а соответствующий строгий технический термин - компьютер общего назначения - КОН. Слово «большой» не следует воспринимать буквально. Оно не означает размеры, а происходит из пословицы: «большому кораблю - большое плавание». Так называют компьютер, предназначенный для решения широкого круга задач, в том числе сложных. В качестве «больших» используются достаточно мощные компьютеры, укомплектованные соответствующими процессорами и периферийными устройствами, оперативной памятью большого объема, а также внешней памятью для долговременного хранения множества программ. Соответственно цена такой машины может кусаться более или менее сильно. Именно к классу «больших» относятся наши настольные компьютеры, ноутбуки и планшеты, современные смартфоны, а также серверы, используемые профессионалами на предприятиях и в дата-центрах, и маленькие на вид одноплатные компьютеры типа Raspberry Pi, Orange Pi, Odroid и многие другие.
Микроконтроллер (МК) похож на компьютер, но отличается тем, что соединен напрямую с какими-либо техническими устройствами и управляет ими без участия человека. Устройства ввода применительно к микроконтроллерам называются датчиками, устройства вывода - исполнительными механизмами. Микроконтроллер может вообще не иметь никаких органов управления для взаимодействия с человеком.
Ближайшие родственники микроконтроллеров - промышленные роботы и регуляторы, станки с ЧПУ, а также боевые, корабельные и авиационно-космические счетно-решающие приборы. С точки зрения программиста между ними вообще никакой разницы нет. Различие здесь конструктивное. Такие приборы обычно имеют массивный, прочный корпус (пластмассовый или металлический), защищающий хрупкую «начинку» от влаги, пыли и других нежелательных воздействий, а на входах и выходах - винтовые зажимы для присоединения достаточно толстых проводов и электронную «обвязку», обеспечивающую прямое подключение к соответствующим датчикам и механизмам, защищающую микроконтроллер от превышения допустимого тока и напряжения. Неудивительно, что качественный промышленный контроллер может стоить дороже домашне-офисного КОН, хотя значительно уступает ему по своим «умственным способностям».
Программное обеспечение промышленных контроллеров обычно включает некоторые типовые задачи, среди которых N1 - пропорционально-интегрально-дифференциальное регулирование. Это наука институтского уровня, поэтому мы в этой книге рассматривать эту задачу не будем. В двух словах: принцип пропорционально-интегрально-дифференциального регулирования позволяет быстро и точно управлять трудноуправляемыми в обычных условиях техническими устройствами, такими как корабельная артиллерийская башня или космическая ракета, хотя подобные объекты могут встретиться практически в любой области техники.
Более подробно о программном обеспечении, о различии программ для компьютеров и микроконтроллеров поговорим позже.
Граница между компьютерами и микроконтроллерами не очень четкая: некоторые микроконтроллеры имеют клавиатуры и экраны, а у некоторых компьютеров соответствующие устройства могут быть сильно упрощенными… Некоторые специалисты выделяют в отдельный класс специализированные компьютеры, и если принять такую трактовку, то место этому классу как раз между компьютерами и микроконтроллерами.
Специализированные устройства («гаджеты») содержат в себе компьютер (скорее микроконтроллер) и какие-то периферийные устройства «в одном флаконе» и предназначены для решения каких-то специфических задач. Примеры: телефонные аппараты, радиостанции, аудио- и видеоплееры, электронные книги («читалки»), фотоаппараты, видеокамеры, автоматизированная бытовая техника… Граница между гаджетами и специализированными компьютерами опять-таки размыта. К какой из этих групп отнести, например, морской карт-плоттер?
Программируемые логические контроллеры (ПЛК) и программируемые логические интегральные схемы (ПЛИС) - еще один класс (или все-таки два класса?) приборов, которые вам могут встретиться «вживую» или в книгах и которые мы не должны забыть, говоря о компьютерах и микроконтроллерах. В промышленной автоматике ПЛК работают «плечом к плечу» с компьютерами, но сами они компьютерами не являются, поскольку не обладают памятью: состояние их выходов определяется состоянием входов в данный момент. Соответственно ПЛК не выполняют расчетов по сложным алгоритмам, в которых результат операции используется для последующих операций. К сожалению, здесь имеется путаница в терминологии: часто словом ПЛК называют промышленные регуляторы. ПЛИС похожа ПЛК и в то же время на микроконтроллер, и может считаться чем-то средним между ними, но еще больше она похожа на набор запчастей для самостоятельной сборки микроконтроллера. Только собирать его приходится не болтами и пайкой, а логически. Процесс изготовления микроконтроллера на основе ПЛИС аналогичен программированию: на некотором языке составляется описание устройства, затем это описание компилируется на компьютере и «заливается» в ПЛИС. Чаще всего используется язык Verilog.
Контроллеры на основе ПЛИС дороже обычных микроконтроллеров, для большинства обычных программистов это полнейшая terra incognita, зато ПЛИС могут решать некоторые специфические задачи намного быстрее обычных микроконтроллеров и поэтому имеют свою пусть небольшую, но устойчивую область применения. Одноплатный промышленный контроллер на ПЛИС по внешнему виду (даже с открытым корпусом) трудноотличим от обычного одноплатного МК.
Отдельную группу компьютероподобных устройств представляют калькуляторы. Настоящий калькулятор характеризуется тем, что может выполнять только одиночные действия. Но как быть с программируемыми калькуляторами? Программируемый калькулятор - по сути стопроцентный компьютер, так что давайте просто считать это название досадной неточностью в терминологии.

Информация и ее представление в компьютере

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

Единицы измерения информации

Минимальная единица - бит (англ. bit). 1 бит - это количество информации, которое мы рассчитываем получить в ответ на вопрос, если ответ может быть «да» или «нет». Для полной строгости следовало бы добавить: при условии, что эти «да» и «нет» равновероятны, но в нашей повседневной компьютерной жизни этим обычно пренебрегают. Бит - это элементарная ячейка компьютерной памяти, которая в каждый момент имеет одно из двух возможных значений. Как называть эти значения? - Да как угодно. «Да» или «нет», «истина» или «ложь», 1 или 0.
Как видим, бит - очень маленькая единица. Если ее не хватает, приходится оперировать более крупными.
Первая более крупная единица информации - байт - 8 бит. Это ячейка компьютерной памяти, способная принимать 256 различных значений. Как трактовать эти значения? - Можно по-разному. Вот самые типичные:
* Натуральные числа от 0 до 255;
* Целые числа от -128 до 127;
* Буквы, цифры и разные другие символы, которые встречаются в книгах и деловых документах.
Если с первыми двумя случаями все ясно, то на последнем чуть позже остановимся более подробно.
Что такое слово? - Такой вопрос тоже лучше не задавать. Что такое компьютерное слово? - На этот вопрос ответить можно, хотя и неоднозначно. Машинное слово - это такое количество байт или бит (разрядов), которое данная машина может обработать одной командой: 16, 32 или 64 бита. Если же мы не говорим «машинное», то слово (WORD) - это 2 байта или 16 бит, тогда 32 бита (4 байта) - это двойное слово - double word (DWORD), а 64 бита (8 байт) - четверное слово - four word (FWORD) или quadra word (QWORD). Это современное представление. Если вам в руки попадет книжка 1970-х-1980-х годов про ЭВМ ЕС, то там вы можете увидеть полуслово (16 бит) и слово (32 бита), но это все в прошлом.
Наверно, мы могли бы придумать единицы информации в 10 или 100 байт, но в реальном компьютерном мире такие единицы не имеют употребления.
Ячейка памяти в одно слово может, очевидно, трактоваться как натуральное число в диапазоне от 0 до 65535, или как целое число в диапазоне от -32768 до 32767. Какие числа могут быть представлены двойным или четверным словом, сосчитайте сами.
Сейчас я хочу обратить ваше внимание на то, что ТРАКТОВКА компьютерных данных - вопрос чисто человеческий, сам компьютер ничего никак не трактует - он просто выполняет операции над данными строго по командам, которые мы для него написали. Соответственно забота о том, как правильно написать команды, лежит полностью на программисте. (Точно так же автомобиль сам не знает, куда нужно ехать, - куда рулишь, туда он и едет). И еще: когда мы отводим некоторую ячейку компьютерной памяти для хранения некоторой величины (переменной), мы всегда должны четко представлять размер этой ячейки и столь же четко доводить наше представление до компьютера.
Понятия слова, двойного слова и т. д. ассоциируются с ячейками памяти. Но для решения какой-либо задачи обычно требуется не одна ячейка, а много. Иногда - очень много. Чтобы оценить емкость памяти реального компьютера, нужны кратные единицы: килобайт (Кбайт), мегабайт (Мбайт) и т. д. В отличие от килограмма или километра, килобайт содержит не ровно 1000, а 1024 байта. Сколько в мегабайте - сосчитайте сами.
В 70-е-80-е годы при описании существующих тогда машин емкость памяти часто указывали в машинных словах, что затрудняло сравнение: ведь машинное слово в разных машинах имеет разную длину. Поэтому сейчас емкость любых запоминающих устройств указывают не в машинных словах, а только в байтах.

Представление печатных знаков в компьютере

Есть 10 цифр, 26 общеупотребительных латинских букв (которые бывают заглавные и строчные) и знаки препинания: точка, вопросительный знак и др. - всего порядка сотни знаков, без которых нам не обойтись, независимо от того, в какой стране мы живем. У нас же, россиян, есть еще 33 буквы, которые опять-таки бывают заглавные и строчные. Если сложить все вместе, то одного байта будет достаточно. Однако если мы захотим одной ячейкой памяти представить все буквы всех языков мира, то 256 значений будет недостаточно - значит, нужна ячейка с бОльшим количеством бит. Это будет необходимо и для вычислений, если мы хотим работать с большими числами или представлять их с большой точностью.

Как числа представляются в компьютере и как они записываются в программах

Выше мы рассмотрели байт, слово и двойное слово как запоминающие ячейки, построенные из нескольких элементарных запоминающих ячеек по 1 биту каждая. Размер ячейки в битах в разных книгах может называться длиной, объемом или емкостью - единства терминологии здесь, к сожалению, нет. В зависимости от длины ячейка может принимать то или иное множество состояний, и каждое состояние может трактоваться как число. Если каждый бит изобразить цифрой 0 или 1, то получается, что любое число в компьютере может быть представлено последовательностью двоичных цифр - нулей и единиц. Таким образом мы приходим к пониманию двоичной системы счисления. Эта система для нас непривычна ровно постольку, поскольку мы все с детства привыкли к десятичной системе счисления. Однако десятичная система была не везде и не всегда. В древности в Европе была в ходу двенадцатиричная система. Ее основанием было число «дюжина», которое было удобно тем, что делилось без остатка на два, на три и на четыре. Затем она была вытеснена десятичной системой, которую привезли из Индии арабы. Привезли вместе с привычными нам десятичными цифрами 0123456789, которые часто называют арабскими, хотя на самом деле они индийские. Эта система удобна для счета на пальцах, практиковавшегося в древней школе. Произошло это очень давно, задолго до начала развития компьютерной техники, так что двенадцатиричная система оказалась совершенно забыта и заброшена, и все, что от нее осталось, - это слово «дюжина». Неудивительно, что для современного человека переход на другую систему счисления - психологический барьер… Но для нас, программистов 21-го века, этот барьер уже не имеет сколько-нибудь существенного значения. Вот для инженеров-электронщиков, проектирующих компьютерное «железо» - да… Я же сейчас нарушу общепринятый в компьютерной литературе подход и втравливать читателя в дебри двоичной арифметики не буду совсем. Лишь немного коснусь двоичной записи чисел. Поскольку каждая цифра в двоичной системе не может быть больше 1, то число 2 записывается как 10, 4 - как 100… У одного моего друга - профессионального компьютерщика - номер на двери квартиры записан как 11010, а вам я предлагаю самостоятельно вычислить, какие номера написаны на соседних квартирах, с учетом того что их хозяева - не профессиональные компьютерщики.
Недостаток двоичной системы в том, что запись чисел получается довольно длинной и поэтому трудно читаемой. Например, такое небольшое число как тысяча записывается вот так: 1111101000, а попробуйте записать миллион или миллиард. Поэтому были предложены системы записи чисел: восьмиричная и шестнадцатиричная. Это не самостоятельные системы счисления, а именно системы записи чисел, за которыми, в конечном счете, стоит двоичная система счисления. Весь ряд бит, изображающий число, делится на группы по 3 или 4 бита, и каждая группа изображается цифрой: восьмиричной или шестнадцатиричной соответственно. В качестве восьмиричных используют арабские (индийские!) цифры от 0 до 7. Соответственно тысяча в восьмиричной системе запишется как 1750. Для представления шестнадцатиричных цифр используют опять-таки индийские цифры 0…9, а недостающие цифры заменяют латинскими буквами A…F, так что тысяча запишется как 3E8.
В программировании приходится использовать разные системы записи чисел. Восьмиричные числа вам могут встретиться в старых книгах про СМ ЭВМ, если такие книги вдруг попадут вам в руки. Сейчас эта система не используется по очевидной причине: восьмиричные числа по внешнему виду не отличаются от десятичных, и это может стать причиной путаницы, а путаница для нас - враг N1! Если нужно записать число в шестнадцатиричной системе, то его либо предваряют символами 0x, либо после числа ставят букву h (заметим, что ни x, ни h не являются шестнадцатиричными цифрами!). Если же ни того ни другого нет, то число считается десятичным.
Какую систему записи чисел следует использовать программисту?
Еще лет 40 назад приходилось использовать ту систему, которая предписывалась эксплуатационной документацией на конкретную машину. Например для СМ ЭВМ - восьмиричную, а для ЕС ЭВМ - шестнадцатиричную. Рассмотрим пример, хотя и не из реальной жизни, но иллюстрирующий применение разных систем счисления и записи чисел. Допустим, имеем некую абстрактную машину, на которой мы хотим запрограммировать системный вызов: вывести на принтер 50 знаков равенства подряд, чтобы обозначить границу страниц в выходном документе. Программист 60-х годов, работающий в шестнадцатиричной системе, записал бы это примерно так:

mov  r0,31  ;номер системного вызова "вывод N знаков на устройство"
mov  r1,6С  ;устройство, в которое пишем: 108 - номер принтера
mov  r2,3D  ;код знака равенства
mov  r3,32  ;количество знаков для вывода - 50
int  21     ;собственно системный вызов

Но это все в прошлом, современные системы программирования допускают использование таких систем записи чисел, которые наиболее удобны для каждой конкретной операции. Есть несколько разумных рекомендаций.
* Все количественные значения - в десятичной системе.
* Если нужно указать печатный знак, то указываем сам этот знак, заключая его в кавычки (одинарные или двойные - уточнить в документации на машину или систему программирования).
* Неколичественные значения, которые мы не выбираем произвольно, а берем из каких-то справочных пособий, пишем строго так, как они записаны в соответствующих источниках. Например номера системных вызовов DOS/Windows/Linux - в шестнадцатиричной системе, однако еще более грамотно будет не писать коды, а присвоить им символические имена.
* В двоичной системе записывать только те значения, в которых важен каждый бит, например данные для записи в управляющие регистры устройств ввода-вывода. Хотя и здесь будут уместны символические имена.
С учетом сказанного программа из предыдущего примера может быть записана так:

mov  r0,0x31    ;номер системного вызова "вывод N знаков на устройство"
mov  r1,PRINTER ;устройство, в которое пишем
mov  r2,'='     ;знак, который пишем в устройство
mov  r3,50      ;количество знаков для вывода
int  SYSCALL

Программа стала более наглядной, не правда ли? Сразу оговоримся, что PRINTER и SYSCALL - это символические имена, за которыми стоят, в конечном счете, некоторые значения (в первом варианте программы мы их уже писали). И сразу - правило N1 для программиста на все времена: если мы используем какое-то символическое имя, то необходимо убедиться, что это имя определено, т. е. где-то в программе имеется предложение, присваивающее этому символическому имени значение. Как правило, определять имя следует прежде (выше по тексту), чем его использовать.
Мы рассмотрели представление натуральных чисел. Часто бывают нужны целые числа: положительные и отрицательные. Если мы хотим представлять числа одним байтом, то придется пожертвовать диапазоном представляемых чисел: «старший» бит в байте отведем под знак числа. Нулевое значение этого бита будем трактовать как положительное число, тогда 00000000 остается нулем, а значения с 00000001 по 01111111 будем трактовать как числа с 1 по 127 - точно так же, как натуральные. Отрицательные числа имеют единицу в «старшем» бите: значение 11111111 трактуется как -1, а 10000000 как -128. Такое представление чисел называется дополнительным кодом. Оно несколько трудновато для человека, но очень удобно для машины (и соответственно для людей, проектирующих машину), т. к. операции сложения и вычитания производятся совершенно одинаково как для натуральных чисел, так и для целых в дополнительном коде.
А как быть, если нужны дробные числа?
Вообще-то больше половины (а может быть 3/4 или даже больше) всех компьютеров, программ и программистов дробными числами не пользуются и живут при этом припеваючи (и даже прибыль получаючи!). Тем не менее…
Можно, например, трактовать значения байта как дробные числа в диапазоне от 0 (00000000) до 0.99619375 (11111111). Если же нужны числа с целой и дробной частью, то одним байтом здесь не обойдется. Можно, допустим, представить число как двойное слово: «старшее» слово - целая часть, «младшее» - дробная часть. Такое представление чисел называется представление с фиксированной границей целой и дробной частей или представление с фиксированной точкой (запятой). Точка или запятая? - здесь, к сожалению, единства не будет, поскольку в русской литературе и в русской школе принято целую часть от дробной отделять запятой, а в англо-американской - точкой. Эта ситуация всем уже порядком надоела, поэтому программисты в разговоре между собой говорят просто «фиксированное представление чисел». Такое представление имеет ограниченное применение в области бухгалтерско-экономической (поскольку денежные суммы, как правило, содержат некоторое количество целых рублей, а суммы мельче копейки никто не считает) и в программировании обработки деталей на станках с ЧПУ. В физико-математических расчетах обычно применяется представление чисел с плавающей запятой (точкой). Слово «плавающая» здесь - антоним слова «фиксированая». Под такое число отводится ячейка не менее 32 бит, из которых большая часть (примерно три четверти от общего количества бит) отводятся под мантиссу, а остальные - под порядок. Мантисса трактуется как дробное число со знаком, ее абсолютное значение (модуль) всегда находится в диапазоне от 0.5 до 1, и это значение умножается на 2 в такой степени, каков порядок (он трактуется как целое число со знаком). Получается очень большой диапазон представляемых чисел и очень высокая точность. Например, если расстояния измерять в километрах, то максимальное значение будет 2127 км - это расстояние до звезд, причем даже не самых близких к нам, а минимальное (2-128 км) - гораздо меньше размера атома. Расстояние от Земли до Солнца можно представить с погрешностью порядка 20 км, а расстояние между двумя точками на противоположных сторонах Земли - с погрешностью 1-2 м. Очевидно, для штурманских задач, как на море, так и в авиации, такая точность вполне приемлема. Несмотря на это, в мощных процессорах предусмотрена возможность вычислений с числами двойной длины (64 бита и даже 80 бит). В повседневной жизни не каждому программисту это может хотя бы раз понадобиться, но в учебнике высшей математики такие задачи рассматриваются. Процессоры среднего класса (ARM Cortex) обходятся 32-битными числами с плавающей точкой, что соответствует их разрядности.
Число с плавающей точкой в формате 32 бит в программах обозначается словом single, 64 бит - double («двойное»), 80 бит - extended («растянутое»).
В математике иногда приходится оперировать иррациональными числами. Для представления такого числа необходим бесконечный ряд цифр, что в компьютерной технике совершенно нереально. Поэтому в обычном программировании приходится довольствоваться приближенным представлением чисел, которое мы только что обсудили.

Как данные размещаются в памяти компьютера

В предыдущей главе мы говорили о числах, теперь будем использовать более правильный термин: переменная. Этим термином в программировании называется любая величина, которая используется при решении задачи - совсем не обязательно число.
Если раньше мы использовали абстрактное выражение «запоминающая ячейка», то настало время кое-что конкретизировать. Оперативное запоминающее устройство, или оперативная память, предназначена для хранения данных, которые в данный момент участвуют в решении задачи на компьютере. Современная оперативная память всегда имеет байтовую структуру: она организована как ряд ячеек размером в 1 байт каждая, и все ячейки пронумерованы последовательно от нуля до максимума. В компьютерной технике и в программировании номера ячеек принято называть адресами. Если вы когда-нибудь будете читать английские книги в оригинале, то там под словом address может пониматься как адрес, так и сама ячейка.
Как мы уже видели в предыдущей главе, каждая переменная может требовать для размещения не один байт, а более. В этом случае для ее хранения отводится участок или область памяти из нескольких последовательно расположенный байт, причем адрес того байта, который ближе к началу, отождествляется с адресом всей переменной. Теперь для того, чтобы присвоить переменной какое-то значение, даем команду «поместить это значение по указанному адресу» («записать в память»), а чтобы использовать это значение в последующих операциях - команду «прочитать из памяти по указанному адресу». Иными словами, роль и место переменной в расчетах ассоциируется с ее адресом.
Современный программист при написании программы никогда не указывает адрес ячейки: такая программа получилась бы очень неудобной для чтения. Поэтому в тексте программы вместо адреса указывается символическое имя переменной, или просто имя. Имя может содержать одну или более латинских букв, причем на последующих местах, кроме букв, можно также использовать цифры и знак подчеркивания. В некоторых системах программирования допускаются еще какие-то символы - а вот этого я вам делать не советую. Можно ли использовать русские буквы? - Не следует, т. к. ваша программа окажется непригодна для нерусских компьютеров. Латинские буквы рекомендуется использовать заглавные. Возможность использования строчных букв следует уточнить по документации к вашей системе программирования, при этом также надо уточнить, будет ли строчная латинская буква считаться совпадающей с соответствующей заглавной или это разные буквы. В книгах по некоторым системам программирования можно увидеть советы типа «одно и то же имя можно написать как RESET, Reset или reset» - вот с этим я категорически согласиться не могу. Скажем так: данный конкретный транслятор прощает такую вольность. Но некрасивые привычки лучше в себе не воспитывать с молодости, чтобы потом не избавляться от них с мучительной болью. А привычка писать одно и то же имя по-разному - однозначно некрасивая, потому что не способствует ни четкости мысли, ни ясности ее изложения! С другой стороны, привычка всегда и везде писать служебные слова языка строчными буквами, а имена переменных заглавными однозначно будет принята любым транслятором и сделает вам хороший имидж в глазах товарищей-программистов.
Итак, мы пришли к пониманию, что с точки зрения человека имя переменной ассоциируется с ее значением, а с точки зрения компьютера имя просто представляет адрес. То есть между именами и адресами имеется взаимно-однозначное соответствие. Как это соответствие устанавливается?
Первая мысль: написать команду типа «имени переменой SHIRINA поставить в соответствие адрес 14878». На самом деле так тоже никто не делает. В современном программировании мы просто указываем компьютеру: «для переменной SHIRINA отвести 4 байта» или даже еще круче: «для переменной SHIRINA отвести место, как для числа с плавающей точкой». Такой информации достаточно для того, чтобы компьютер сам «оформил землеотвод» по некоторому адресу, а программисту этот адрес чаще всего знать вообще без надобности.
Как компьютер решает задачу размещения переменных в памяти?
Есть два подхода: статический и динамический. Вопреки обычаю, начну с более сложного - динамического. При использовании этого подхода в памяти отводится большая область, которая называется «куча» (англ. heap). Когда возникает нужда создать переменную, компьютер выполняет определенные действия, по итогам которых обнаруживаем отведенный под эту переменную участок в куче, адрес этого участка ассоциируется с именем переменной, а участок помечается как занятый, чтобы не использовать его для чего-то другого. Когда переменная отработает свое назначение, ее нельзя ни оставлять в заброшенном состоянии, ни выбрасывать просто так на свалку: ее необходимо надлежащим образом утилизировать. Для этого имеется специальная команда dispose. Если этого не сделать, то отведенный под переменную участок останется помеченным как занятый (или еще хуже: не будет помечен ни как занятый, ни как свободный), и мы не сможем использовать его для других целей. Такое негативное явление, называемое утечкой памяти (англ. memory leak), типично для не очень опытных и не очень добросовестных программистов, каковых в мире большинство. Проектировщики систем программирования пытаются как-то помочь таким программистам, но с переменным успехом, и вообще на мой взгляд это работа из серии «нос вытащишь - хвост увязнет». Короче, давайте на сегодня считать, что динамическое размещение переменных в памяти - фича для продвинутых программистов.
Суть статического подхода в том, что для всех переменных место в памяти отводится заранее и остается занятым в течение всего времени выполнения программы, т. е. переменная, которая больше не нужна, не уничтожается. С точки зрения чистой экономики такой подход представляется не очень эффективным. Однако сейчас не древний век и мы не покупаем ячейки памяти по одной - покупаем целый компьютер, так что объем памяти обычно бывает с запасом. Иногда - с очень большим запасом. В этой ситуации статическое распределение памяти вполне приемлемо, а в микроконтроллерах практически только так и делается.
Вот пример на языке Паскаль:

var
  Cena:integer;
  Stoim:longint;
  Dlina,Shirina,Vysota,Objem,Ves:real;

Рассмотрим подробно, что здесь написано. Служебное слово var (от variable - переменная) означает, что далее следуют описания всех переменных, которые используются в программе. Эти же описания рассматриваются компьютером как указания отвести участки оперативной памяти под переменные, причем без заполнения этих участков какими-либо начальными значениями (в противном случае вместо var следовало бы написать const). Далее следуют имена переменных и указания их типов: integer - обычное целое число (в Паскале - 2 байта), longint - «длинное» целое число (4 байта). Английскому слову real в русскоязычной математике соответствует термин «вещественное число». Ну мы-то знаем, что настоящих вещественных чисел в компьютерной технике не бывает, так что этому слову соответствует переменная с плавающей точкой… а какой длины? Это нам, по большому счету, не очень-то и важно: компилятор языка Паскаль знает это лучше нас.
Обратите внимание, как в этом примере написаны имена переменных: с заглавной буквы, а последующие буквы - строчные. Это давняя паскалевская традиция. Паскаль - язык строгих правил: на каком бы компьютере он ни был реализован, такое написание не вызовет возражений. Тем не менее в других языках эта традиция может не поддерживаться.
На языке Си этот же пример может быть записан так:

int CENA;
longint STOIM;
single DLINA,SHIRINA,VYSOTA,OBJEM,VES;

Похоже, не правда ли?
Си - язык менее строгих правил, чем Паскаль, но реализован на более широком множестве компьютеров. Поэтому какой-то компьютер может не понять тип real: ведь «вещественные» числа в 32 бита (single), 64 (double) и 80 бит (extended) - они же все real, а тип single однозначно говорит о том, что мы хотим 32-разрядные числа с плавающей точкой. Но компьютер попроще, не «переваривающий» длинные числа с плавающей точкой, может не понять, что такое single. В общем, придется уточнять по мануалам. Слова var или const на сях не пишутся, и это скорее плохо, чем хорошо: экономия трех нажатий на клавиши нас не волнует, а наглядность программы страдает.
И вот тот же пример на языке ассемблера.

dataseg
CENA     dw  ?
STOIM    dd  ?
DLINA    dd  ?
SHIRINA  dd  ?
VYSOTA   dd  ?
OBJEM    dd  ?
VES      dd  ?

Слово dataseg соответствует паскалевскому var и говорит о том, что последующие команды (правильнее было бы сказать директивы) относятся к той области памяти, которая предназначена для данных. Далее каждая переменная декларируется в отдельной строке. Ассемблер не хочет задумываться, будем ли мы использовать числа как целые или с плавающей точкой: ему подавай чисто размер ячейки. И мы ему подаем размер: db - один байт, dw - слово, dd - двойное слово, df (или возможно в каких-то ассемблерах dq) - четверное слово. Далее обязательно начальное значение ячейки, но мы ставим знак вопроса, потому что только запрашиваем ячейки, без записи каких-либо начальных значений.
Важное замечание. Декларируя переменные, мы не только «заказываем» компьютеру место для их хранения, но и даем обязательство, что заявленные здесь имена будут использоваться только как имена переменных, а не для каких-либо еще целей. В программировании каждое слово, каждый знак должен использоваться строго для того, для чего он предназначен, иначе не просто нас не поймут - мы сами себя не поймем!

Имущество твердое и мягкое

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

Что нужно знать о конструкции компьютеров

Прежде всего учтем, что компьютер - электронное устройство, изготовлением компьютеров занимается промышленность: радиоэлектронная или приборостроительная. Так было не всегда: первые компьютеры были механическими.
Обычно компьютер или иное подобное устройство собирается из одного или нескольких блоков (модулей), каждый их которых представляет собой печатную плату со смонтированными на ней электронными компонентами.
Печатная плата представляет собой пластинку из электроизоляционного материала, на поверхности которой имеются медные дорожки, заменяющие провода. Название «печатная» отражает тот факт, что дорожки не прокладываются каждая от начала к концу, а печатаются подобно гравюре все сразу (а на сложной плате их могут быть тысячи). Современная радиоэлектронная промышленность без печатных плат немыслима, но простенькую плату можно изготовить и в домашних условиях (в то же время и заводы не брезгуют частными заказами на изготовление плат из отходов основного производства).
На печатной плате размещаются и закрепляются с помощью пайки компоненты компьютера или иного электронного устройства. Их разнообразие не поддается счету, но под номером 1 у нас будут микросхемы, потому что микроконтроллер, процессор, память - все эти элементы, которые мы рассмотрим чуть позже, выполняются в виде микросхем.
Микросхема представляет собой, по большому счету, ту же печатную плату, только уменьшенную во много раз. На кремниевой пластинке (ее называют кристаллом или чипом, от англ. chip - щепка, стружка) «напечатаны» тысячи, а иногда миллионы электронных компонентов и связей между ними, а для связи с внешним миром к кристаллу подпаяны тончайшие медные проволочки. Размеры чипа могут быть порядка 2..6 мм, так что ясно, что устройство получается весьма нежное. Чтобы защитить микросхему от внешних воздействий, ее заключают в корпус из твердой, почти негорючей пластмассы обычно черного цвета, а для электрических соединений микросхему снабжают металлическими выводами такой толщины, чтобы они хотя бы могли выдержать прикосновение человеческих пальцев. Форму и размеры корпуса конструктор выбирает не как попало, а согласно одному из общепризнанных стандартов. Их много, мы сейчас остановимся на трех.
* Корпуса QFP и SOIC - «планарные», или SMD - surface-mounted details - «детали для поверхностного монтажа». Корпус квадратный (QFP - Quad Flat Package, с вариантами: BQFP, TQFP и др.) или продолговатый (SOIC), выводы («ножки») расположены по кромкам корпуса (у QFP по всем 4 сторонам, у SOIC - только по длинным), и загнуты так, что их концы находятся в плоскости основания корпуса. На поверхности печатной платы под них делаются контактные площадки, и выводы к ним припаиваются. Выводы очень мелкие, поэтому пайка таких микросхем - промышленная технология. Для заводов такая технология - находка, но «не пытайтесь повторить это дома»: следует либо купить микросхему аналогичного назначения в другом корпусе (это в большинстве случаев возможно), либо предпочесть плату заводского изготовления.
* Корпус QFN - квадратный, без «ног» - выводами служат контактные площадки, расположенные по краям основания. Такая микросхема не паяется, а вкладывается в держатель (по-простонародному называемый панелькой или кроваткой), который паяется на плату. К такой технологии прибегают, если требуется, чтобы микросхема была быстросменной.
* Корпус CBGA похож на QFN и отличается тем, что контактные площадки размещены по всему основанию корпуса рядами («матрицей»).
* Корпус PDIP имеет продолговатую форму и выводы только по продольным сторонам. Выводы загнуты вниз. В печатной плате под них сверлятся отверстия, контактные площадки делаются на обратной стороне платы, и пайка выполняется тоже с обратной стороны. Для заводов такая технология неудобна, а вот для радиолюбителя - самое то что надо. Если же пайка «намертво» нежелательна, то для таких микросхем имеются и «кроватки».
Для примера: микроконтроллер ATmega8 выпускается в корпусах TQFP32, QFN32 и PDIP28. Цифры в обозначении корпусов - количество выводов.
Теперь, когда мы получили представление о печатных платах и микросхемах, нам нетрудно понять, что такое однокристальный микроконтроллер или одноплатный компьютер.

Общие принципы архитектуры

Основные части, без которых ни одно компьютероподобное устройство не может обойтись, - это процессор и оперативное запоминающее устройство (ОЗУ, оно же оперативная память, по-английски RAM - Random Access Memory - память с произвольным доступом).
Процессор (англ. processor - обработчик) - это «сердце - пламенный мотор» компьютера. Здесь выполняются все операции по преобразованию данных. А хранятся данные в оперативной памяти. Процессор, как правило, имеет лишь несколько запоминающих ячеек - регистров, в которых данные находятся в течение очень короткого времени перед операциями и после них. Мощные современные процессоры часто имеют кэш - запоминающее устройство типа ОЗУ, но меньшей емкости. Кэш занимает промежуточное место между ОЗУ и регистрами процессора. Для программиста он, как принято говорить, «прозрачен», т. е. программист как бы не видит кэша и не догадывается о его существовании, и пишет программу так, как будто никакого кэша нет. Наверно, мне вообще не следовало бы здесь писать о кэше, но вам это слово может встретиться, например, в описании процессора, значит вам надо знать, что это слово значит.
Количество регистров в современных процессорах обычно бывает 16 или 32, а емкость оперативной памяти от 128 байт у ATtiny25 до 16 Гбайт и возможно больше у «больших» компьютеров.
Процессор и ОЗУ соединены между собой шиной. По большому счету, шина - это просто множество проводов. У сложных мощных компьютеров в состав шины входят определенные электронные компоненты - чипсет, и это я вам пишу опять-таки только для того, чтобы это слово не стало для вас неожиданностью, если вы его где-нибудь увидите. И еще пара выражений, которые вы можете услышать от специалистов: северный мост и южный мост - это не что иное как составные части чипсета в компьютерах архитектуры IBM.
Процессор и оперативная память представляют собой ядро компьютера и, как я уже говорил, это обязательные компоненты. Все остальное - периферия, или периферийные устройства. Их разнообразие огромно: мониторы, клавиатуры, принтеры, звуковые, сетевые устройства и другое. На этом мы остановимся чуть позже. Сейчас важно, что (1) ни одно из этих устройств не является обязательным и (2) обычно любое периферийное устройство может быть заменено другим устройством, близким по назначению, без существенного ущерба для работы программ на этом компьютере.
Периферия соединяется с процессором (и иногда с оперативной памятью) с помощью шины. И вот тут мы подошли к первому архитектурному вопросу, который программисту необходимо прояснить, прежде чем приступать собственно к программированию.
Вопрос вот в чем. Шина может быть либо одна - общая для памяти и периферийных устройств, либо их будет две: шина памяти и шина ввода-вывода (как во всех наших настольных компьютерах и ноутбуках). Соответственно по первому варианту программист обращается к периферийным устройствам как к ячейкам памяти, для которых отведена определенная область адресного пространства (обычно не более четверти того, что отведено под оперативную память). Во втором случае для обращения к периферийным устройствам имеются специальные команды «ввод» и «вывод», и адресное пространство периферийных устройств - свое, отдельное от ОЗУ. Иными словами, команды «ВВОД ИЗ УСТРОЙСТВА ПО АДРЕСУ 108» и «ЧТЕНИЕ ПАМЯТИ ПО АДРЕСУ 108» - это совершенно разные команды.
Следующий архитектурный вопрос: распределение памяти. Здесь два варианта: неймановский и гарвардский. При неймановском варианте (он используется в большинстве компьютеров) имеем одно оперативное ЗУ, в котором для программы и для данных отводятся отдельные области в пределах общей емкости (общего адресного пространства). Сколько отвести под программу и сколько под данные - на этот вопрос никакого стандартного ответа нет: это решается по потребностям для каждой задачи. При гарвардском варианте (он типичен для микроконтроллеров, в т. ч. ATtiny и ATmega) программа и данные находятся в отдельных устройствах и, соответственно, при проектировании нашего изделия мы должны выбрать такой микроконтроллер из семейства, чтобы и память программ, и память данных имели достаточный объем. При этом возможна ситуация, когда память данных соответствует потребности тютелька-в тютельку, а память программ имеет большой избыток емкости (или наоборот).

Разрядность и производительность процессора

Разрядность - важнейший архитектурный показатель процессора (и он же определяет разрядность машины в целом). Это «длина машинного слова», или количество бит, которое данный процессор может обрабатывать одной командой. Разрядность может принимать значения из ряда: 8, 16, 32 и 64. Очевидно, что чем больше разрядность, тем больше диапазон и точность представления числовых величин, с которыми наш процессор может работать, и тем больше данных он способен переработать за единицу времени. Заметим, что процессор, разрядность которого больше 8, обязательно имеет команды для обработки 8-битных величин, а 32-разрядный - также и для 16-битных, и т. д.
Я не случайно подчеркиваю, что разрядность - это архитектурный параметр. Сейчас мы остановимся на двух количественных параметрах: это тактовая частота процессора и количество ядер в нем.
Если процессор - сердце компьютера, то тактовая частота - его ритм сердцебиения. Она определяется тактовыми импульсами, которые вырабатываются тактовым генератором. Любое действие в процессоре занимает один или более тактов, поэтому тактовая частота косвенным образом показывает количество операций, которое процессор может выполнять в секунду. Для Ардуино - даже не косвенно, а почти прямо, а для других процессоров может быть по-разному. Например, процессор Cortex M3 производит сложение, вычитание и умножение за 1 такт, а для деления может потребоваться от 2 до 12 тактов. Поиск аналогичной информации в описаниях и руководствах по процессорам разных типов может оказаться весьма хлопотной работой: два процессора, выпущенные одной фирмой в один год и отличающиеся одной буквой в названии, могут иметь существенные отличия по времени выполнения команд. Поэтому программисту следует всегда держать в уме один простой факт: деление - самая долгая из арифметических операций, в самом лучшем случае оно занимает столько же времени, сколько и умножение, но никогда не быстрее.
Каждый процессор, изготавливаемый на заводе и продающийся в магазинах, имеет максимально допустимую тактовую частоту, которая является его паспортным параметром, т. е. указывается в заводской документации. Именно ее и имеют в виду, когда говорят о производительности процессора. Превышать ее, вообще говоря, не следует. Попытка эксплуатировать процессор на более высокой частоте - разгон процессора - дает результат «фифти-фифти», т. е. может удаться, а может и нет - вплоть до полного выхода процессора из строя. Как правило, процессор при превышении тактовой частоты потребляет больше электроэнергии и сильно греется, так что усиленное охлаждение дает некоторый шанс на успех.
Строительство многоядерных процессоров - прием, позволяющий увеличить скорость обработки данных без чрезмерного увеличения тактовой частоты (что сопряжено с определенными трудностями) и без увеличения количества процессоров в компьютере (что резко усложнит систему и уменьшит надежность). Простые процессоры имеют одно ядро. Когда мы говорим «многоядерный процессор», то подразумеваем, что он 32- или 64-разрядный (количество ядер может быть 2, 4, 8, 16 и т. д.)
Производительность процессора - это объем данных, который он может «переварить» за секунду. Производительность можно определить также и для машины в целом, с учетом объема (объемов) запоминающих устройств, но это сложно, поэтому сейчас остановимся только на производительности процессора. Будем ее рассчитывать по формуле:
P=B*C*F
где P - производительность, B - bits - разрядность, C - cores - количество ядер, F - frequency - тактовая частота.
Обратите внимание, что я использую звездочку как знак умножения. В программировании - только так.
Эта формула приблизительна.
Во-первых, если ядер несколько, то не каждый программист сможет распределить объем работы между ними поровну (и уж точно не сможет, если задача такова, что не позволяет такого распределения). Во-вторых, ядра, работая каждое над своей частью задачи, хоть чуть-чуть, но мешают друг другу при обращении к памяти и периферии, из-за чего производительность неизбежно страдает. С другой стороны, проектировщики мощных процессоров (32- и особенно 64-разрядных), помимо чисто количественного наращивания сил, применяют различные технические ухищрения для уменьшения затрат времени на каждую операцию, так что реальная производительность может оказаться намного больше.
Почему я различаю разрядность как архитектурный параметр и два других параметра как количественные?
Потому что можно построить 8-разрядный процессор с очень большой тактовой частотой, но программироваться он будет так же, как и с низкой частотой. И наоборот, можно построить 64-разрядный процессор с низкой частотой или еще проще: эксплуатировать его на низкой частоте, и на программировании это опять-таки никак не скажется. В реальной жизни так никто не делает. Люди давно поняли, что процессор с маленькой разрядностью предназначен для несложных задач, и большая тактовая частота ему не нужна. Если хотим получить более высокую производительность, то наращиваем и частоту, и разрядность, а потом и количество ядер.
Производительность - важный параметр, позволяющий нам понять, какой процессор «мощный», какой «маломощный», или сказать, к примеру, что процессор Intel Atom в тысячу раз мощнее ATmega. У профессоров подобные выражения под запретом, но действующие программисты в разговоре между собой считают слово «мощность» нормальным.

Какие бывают запоминающие устройства

Рассмотрим несколько терминов, некоторые из которых характерихуют техническую реализацию устройств, а другие - их место в компьютере, роль в решении задачи на компьютере или соответствие некоторым требованиям. Некоторых из них мы уже касались, сейчас кое-что систематизируем.
Оперативная память предназначена для хранения данных для решения задачи, которая в данный момент решается на компьютере, а в «большом» компьютере - также и для программы. В качестве оперативной используется электронная память, все особенности устройства которой «заточены» на максимальное быстродействие и на исключение каких-либо ограничений по количеству операций записи. Оперативная память энергозависима, т. е. хранит данные постольку, поскольку не нее подано напряжение питания (и, соответственно, при этом имеет место потребление электроэнергии). В англоязычной документации соответствующий термин: RAM - Random Access Memory.
Слова «Оперативная память» означают, с одной стороны, место этого устройства в компьютере и возлагаемую на это устройство роль в работе компьютера, и с другой стороны эти слова характеризуют промышленное изделие, продаваемое в магазинах (и соответственно такая надпись может наноситься на само изделие).
SRAM - Static Random Access Memory: слово static характеризует особенности внутреннего устройства микросхемы памяти, интересные и понятные только для инженера-электронщика. Мне непонятно, зачем это слово вообще упоминается в документации на микроконтроллеры Atmel, но оно там есть, и оно не должно сбивать вас с толку: в этих микроконтроллерах это обычная оперативка, выполненная на одном чипе с процессором и используемая только для хранения данных.
Долговременная память: этот термин подразумевает пригодность для долговременного (и следовательно энергонезависимого) хранения данных, которые не требуют частой и многократной перезаписи (вплоть до таких данных, которые записываются один раз и в дальнейшем много раз читаются). Близкие по значению термины: ROM - Read-Only Memory - «память только для чтения», по-русски ПЗУ - постоянное запоминающее устройство.
EEPROM - Electrically Erasable and Programmable Read-Only Memory - «память только для чтения, программируемая и стираемая электричеством» - вариант электронной памяти для использования в качестве долговременной. Эта память имеет скорость чтения практически как у оперативной, однако запись в такую память требует значительных затрат времени (не столько на саму запись, сколько на предварительное стирание ранее записанных данных). В микроконтроллерах фирмы Atmel имеется от 128 до 4096 байт такой памяти, которая выполнена на одном чипе с процессором, может быть использована в программах и допускает до 100000 циклов перезаписи. В «больших» компьютерах такая память также имеется, но только для собственных нужд компьютера, а для программиста она недоступна.
Флэш-память (англ. flash memory) - название одного из типов энергонезависимой электронной памяти, занимающей промежуточное положение между оперативной памятью и EEPROM. Флэш-память работает медленнее оперативной, но пишется быстрее, чем EEPROM и допускает обычно до 10000 циклов перезаписи. Обычно выпускается в виде микросхем, которые можно увидеть в роутерах, телевизорах и телевизионных приставках и других подобных устройствах для хранения программ (прошивок) с возможностью перепрограммирования, а в микроконтроллерах Atmel флэш-память выполнена на одном чипе с процессором. Также этот тип памяти используется в быстросменных картах памяти («флэшках»), которые в «больших» компьютерах используются в качестве внешней памяти.
Внешняя память: под этим термином подразумеваются различные устройства, по сути представляющие собой память, но подключаемые к компьютеру как устройства ввода-вывода. Техническая реализация внешней памяти может быть различна, но есть общие черты: очень большой объем, достигаемый ценой невозможности быстрого доступа к каждой конкретной ячейке. Устройство внешней памяти принимает от процессора команды чтения и записи данных блоками: к команде записи прилагается блок данных, которые требуется записать, а в ответ на команду чтения блока устройство сообщает процессору требуемые данные. Чтение и запись происходят значительно медленнее, чем в оперативной памяти, поэтому процессор никогда не обрабатывает данные из внешней памяти непосредственно: он дает команду чтения, помещает данные «оптом» в оперативную память, выполняет всю обработку там, а результаты, если нужно, помещает во внешнюю память. В «больших» компьютерах все программы для решения множества задач, а также и данные для их работы, хранятся именно во внешней памяти, где они организованы в виде файловой системы, и при необходимости копируются в оперативную память, где и ведется вся обработка.
Здесь рассмотрена общепринятая трактовка термина «внешняя память», но применительно к микроконтроллерам Atmel этот термин имеет другое значение: оперативная память в виде отдельной микросхемы, в отличие от той, которая расположена на одном чипе с процессором. Настоящей внешней памяти в таких микроконтроллерах чаще всего не бывает.
«Винчестер» - несменный жесткий магнитный диск - устройство номер 1 для использования в качестве внешней памяти в «больших» компьютерах. В качестве устройства номер 2 здесь будут вышеупомянутые флэш-карты, а под номером 3 - SSD-диски, которые представляют собой не что иное как флэш-микросхемы, подключаемые к компьютеру как винчестеры.
Настоящий винчестер не имеет ограничений по количеству циклов записи, поэтому чаще всего используется в больших компьютерах, а флэш-карты и SSD имеют такие ограничения и поэтому не могут рассматриваться как полноценная замена винчестерам. Их следует использовать как WORM-память (англ. Write Once Read Many - «пишем один раз, читаем много раз»).
Кэш-память - небольшая оперативная память, встроенная в процессор и называемая так только в том случае, если имеется и настоящая оперативная память вне процессора.
Лазерные оптические диски (CD и DVD) и соответствующие дисководы могут рассматриваться и в качестве памяти, и в качестве устройства ввода-вывода.

Какие бывают периферийные устройства

Мы уже рассмотрели устройства ввода-вывода и внешние запоминающие устройства - все они являются периферийными.
Периферийные устройства соединяются с процессором шиной ввода-вывода (или общей шиной). С точки зрения программиста каждое устройство представлено небольшим количеством запоминающих ячеек - регистров, или портов, ввода-вывода. Мы можем читать их них информацию и записывать свою, которая может восприниматься устройством как команды или как данные. Типична ситуация, когда устройство имеет всего два порта ввода-вывода: порт данных и порт управления (команд, настроек). Основная сложность работы программиста с периферийными устройствами состоит в том, что нужно подробно изучить каждое устройство (а их много, и все разные!), чтобы понять, что куда писать и откуда читать.
Разнообразие периферийных устройств для микроконтроллеров, специализированных компьютеров, гаджетов и больших компьютеров не поддается счету, сейчас мы рассмотрим лишь некоторые из них.
Аналого-цифровые и цифро-аналоговые преобразователи широко используются в промышленной автоматике и, соответственно, в микроконтроллерах. Выше я писал про аналоговый компьютер, в котором каждая величина, участвующая в расчетах («переменная»), может принимать непрерывное множество значений в допустимом интервале. Компьютеры, с которыми нам предстоит иметь дело, - цифровые: в них каждая величина имеет конечный ряд допустимых значений, так что каждое значение аналоговой величины может быть представлено ближайшим значением из ряда допустимых. Чтобы включить такой компьютер в некоторое техническое устройство, приходится ставить соответствующие преобразователи. Роль аналоговых величин чаще всего играет электрическое напряжение, иногда - сила электрического тока. Неэлектрические величины типа температуры, давления, освещенности преобразуются в электрическое напряжение с помощью соответствующих датчиков, которые на рынке представлены как самостоятельные изделия. С другой стороны, электрическое напряжение, выдаваемое компьютером через цифро-аналоговый преобразователь, может быть подано на источник света или на электромотор…
Таймер или правильнее: счетчик-таймер считает поступающие на его вход электрические импульсы: досчитав до установленного значения (или до нуля, если нужное значение было заложено в счетчик перед началом операции), счетчик-таймер посылает микроконтроллеру сигнал, в ответ на который можно запрограммировать те или иные действия. Таймер, в строгом понимании этого слова, обеспечивает выполнение некоторого действия через заданное время после наступления определенного события. Таймер-счетчик считает импульсы, которые совсем необязательно соответствуют каким-то единицам времени. И в любом случае ни одно из этих устройств «не знает», какой сейчас день недели или год: эта задача возложена на другое устройство.
Часы истинного времени или, как часто пишут «часы реального времени» (что не совсем верно), хранят в своих запоминающих ячейках часы и минуты местного времени, число, месяц и год, и обычно день недели впридачу, и могут выдавать эти значения по запросу компьютера. Таким образом можно обеспечить временную привязку действий компьютера не к событию, которое может произойти когда угодно, а к заданному дню и часу. Например, в помещении, где содержат скот или домашнюю птицу, для повышения их продуктивности желательно включать и выключать свет в определенное время. Прибор, который можно построить для решения этой задачи, тоже назыается таймером, но в наше время такой прибор целесообразно построить на микроконтроллере и снабдить его часами истинного времени, во избежание нарушения светового режима при перерывах подачи электроэнергии.
Часы истинного времени обязательны для большого компьютера, а для микроконтроллеров они поставляются как отдельные изделия.
Устройства отображения информации используются тогда, когда вырабатываемая компьютером информация предназначена для восприятия человеком. Персональный компьютер без таких устройств немыслим. В этой группе устройств под номером 1 будет экран. Монитор, или если совсем строго то видеомонитор, - это экран, изготавливаемый промышленностью и продающийся в магазинах как самостоятельное изделие, подключаемое к компьютеру через стандартный интерфейс. Принтер, в терминологии нашей молодости алфавитно-цифровое печатающее устройство (АЦПУ), на программистском жаргоне «печаталка» - тоже относится к устройствам отображения.
Устройства ручного ввода - здесь под номером 1, конечно, клавиатура, а под номером 2 - всевозможные манипуляторы, передающие компьютеру определенные сигналы при перемещении устройства человеком: мыши, трекболы, джойстики, игровые рули и педали…
Звуковые устройства, звуковые карты, звуковые процессоры.
Устройства приема и передачи данных обеспечивают обмен данными между двумя и боле компьютерами, находящимися на значительном расстоянии друг от друга. Такие устройства достаточно разнообразны. Если расстояние не очень велико и имеется возможность проложить провод, то используется проводная связь по интерфейсам SPI (Serial Peripheral Interface), TWI (Two-Wired Interface), RS-232 или Ethernet. Последний достаточно сложен и используется в серьезной технике, начиная с 32-разрядных микроконтроллеров ARM Cortex, в более дешевых устройствах он не получил признания. Передача данных по проводам с помощью электрического тока сопряжена с определенными трудностями. Если говорить по-самому-простому, то можно передавать данные быстро, но недалеко, а можно на большое расстояние, но медленно. От этого недостатка свободен волоконно-оптический (оптоволоконный) кабель, по которому данные передаются не током, а светом. Основной объем «грузооборота» компьютерных данных в мире приходится именно на оптоволоконные сети (в понимании большинства людей они ассоциируются с интернетом - это не совсем верно, но в первом грубом приблиении можно принять).
Если прокладка проводов невозможна или нежелательна, приходится использовать радиоволны: на малых дистанциях WiFi и Bluetooth, на средних - приемопередатчики диапазона 433 МГц, на дальних дистанциях - коммерческие сети GPRS, 3G и 4G.
GPIO - General Purpose Input-Output - ввод-вывод общего назначения - это просто провода (часто заканчивающиеся контактными штырями или гнездами), которые напрямую соединены с портами ввода-вывода, так что мы можем просто снимать с них данные в виде логических нулей и единиц либо посылать туда высокое напряжение (логическую единицу) или низкое (логический 0). GPIO никогда не работают сами по себе - к ним подключаются какие-то другие устройства, и этими устройствами мы можем управлять. GPIO всегда имеется на микроконтроллерах, иногда в больших количествах (десятки отдельных линий), а у больших компьютеров GPIO чаще всего нет.
Периферия больших компьютеров (а часто и микроконтроллеров) обычно строится по двухуровневому принципу: каждое «направление» ввода-вывода обслуживается двумя устройствами, одно из которых - оконечное устройство, а другое - адаптер. Например, монитор - оконечное устройство, а видеокарта - адаптер (ее и называют часто видеоадаптером). Такое кажущееся усложнение системы продиктовано тем, что оконечных устройств очень много, выпускаются они множеством разных фирм, и нет смысла «учить» компьютер работать с каждым из них. Эта задача возлагается на проектировщика адаптера, а проектировщику компьютера и программисту приходится иметь дело только с адаптером.
На самом деле в большом компьютере при наличии операционной системы таких уровней бывает даже не два, а больше, но все остальные уровни представлены виртуальными, или логическими, устройствами, за которыми не стоит никакое «железо» - это просто программы в составе операционной системы. Рассмотрим, что происходит, если программист в системе Linux пишет, допустим, на языке Паскаль writeln или на языке C - printf. Данные, которые он хочет вывести, попадают на стандартный вывод - само это выражение красноречиво говорит о том, что это виртуальное устройство. Стандартный вывод передает данные на следующее виртуальное устройство - обычно на консоль, но смысл хитрости в том, что стандартный вывод можно перенаправить, например на принтер или в файл. Причем делается это средствами операционной системы, без вмешательства в программу, «заказывающую» вывод. С консоли данные направляются либо во фреймбуфер, либо на эмулятор терминала, и уж только оттуда - на видеокарту.
Ужасно сложно, да?
Подобные «сложности» в компьютерном мире - обыкновенная повседневность. На самом деле все это было бы сложно для человека, если бы он затеял в одиночку разрабатывать операционную систему. Но реальную операционную систему создает не один человек - там большой коллектив, в котором каждый программист разрабатывает свой участок. По результатам же этой работы имеем существенное упрощение жизни как для программиста, так и для системного администратора, а в конечном счете и для разработчиков операционной системы, если они захотят что-то в своем изделии переделать.

Программная совместимость и архитектурные классы (аппаратные платформы) компьютеров

Два компьютера называются програмно-совместимыми, если программа, написанная для одного из них, может эксплуатироваться и на другом.
В те времена, когда компьютеры только появились, проблема программной совместимости стояла очень остро: каждый новый компьютер строился с теми или иными усовершенствованиями, и каждое усовершенствование могло привести к тому, что все программное обеспечение для нового компьютера придется создавать заново с чистого листа, а это было чрезвычайно трудоемко. Случалось, что разработка программ просто не успевала за разработкой «железа». С появлением языков высокого уровня острота этой проблемы несколько снизилась: теперь можно было старую программу просто перекомпилировать для нового компьютера. Но это возможно постольку, поскольку доступен исходный текст программы, а так бывает не всегда. Таким образом, в наше время создатель каждого нового компьютера должен серьезно задумываться о том, нужно ли обеспечивать программную совместимость и с какими именно из ранее созданных компьютеров.
Часто бывает так, что некоторый новый компьютер А создается не на пустом месте, а как развитие некоторого более старого компьютера В. В таком случае разработчик обеспечивает программную совместимость «снизу вверх»: программа, созданная для компьютера В, может выполняться и на компьютере А, но не гарантируется, что программа для компьютера А, будет выполняться на В. В дальнейшем может появиться новый компьютер С, и т. д. Таким образом развивается семейство компьютеров, пограммно-совместимых «снизу вверх».
От чего же зависит программная совместимость или несовместимость?
Причиной несовместимости может быть любая мелочь. Но для совместимости необходимо, чтобы процессоры имели, как минимум, одну и ту же систему команд (командный язык). Командный язык непосредственно связан с разрядностью процессора.
Существует много архитектурных классов компьютеров: два компьютера из разных классов категорически не будут иметь программной совместимости, а в пределах одного класса по крайней мере есть надежды на совместимость. Часто употребляется термин «аппаратная платформа».
Из множества архитектурных классов рассмотрим несколько самых перспективных для нас.

AVR

Это «Alf & Vergard RISC» - «компьютер Альфа и Вергарда с сокращенной системой команд». В рамках этого класса фирма Atmel выпускает семейства 8-разрядных однокристалльных микроконтроллеров (ОКМК) ATtiny и ATmega (хотя по большому счету это одно большое семейство). Эти простые машинки - «Жигули» среди компьютеров. Они имеют 32 регистра общего назначения, оперативную память SRAM и долговременную память EEPROM для данных, flash-память для программы (гарвардская архитектура!) и «малый джентльменский набор» периферийных устройств для управления различным оборудованием. Значения емкости запоминающих устройств варьируются в широком диапазоне, за счет чего и получается семейство микроконтроллеров, программно-совместимых с точностью до некоторых нюансов.
Процессоры AVR работают только с целыми числами, в них нет команды деления, а в самых маленьких - даже и умножения. Несмотря на это, выпускаются они в огромных количествах и применяются в самых разных изделиях от игрушек до вполне серьезной промышленной автоматики. На основе этих однокристалльных микроконтроллеров выпускаются одноплатные микроконтроллеры (ОПМК) под торговым названием Arduino. Однако сразу предупреждаю: Arduino - категория не архитектурная, а скорее конструктивная: под этим же названием выпускаются машины ARM Cortex M3 (Arduino Due), а несколько лет назад выпускались и в архитектуре Intel (Arduino Galileo, сейчас их выпуск прекращен).
Кроме Arduino, множество фирм из разных стран выпускает на основе Atmel'овских микроконтроллеров одноплатные микроконтроллеры: SEM - Smart Evolution Module - буквально «хитрый модуль для развития», Seeduino и другие, и можно не сомневаться, что в обозримом будущем появятся еще. В отличие от ардуин, большинство которых (Uno, Nano, Micro, Leonardo, ProMini) использует один и тот же ОКМК ATmega328 (32к+2к памяти), семейство SEM представляет собой гамму микроконтроллеров с широким выбором по емкости памяти: от ATmega48PA (4к+512) до ATmega1248P-AU (128к+16к). Для каждого устройства в скобках указаны величины емкости памяти: flash-памяти + SRAM.
Эти микроконтроллеры очень энергоэкономичны: могут месяцами работать от небольших батареек и при работе практически холодные.
Многие считают AVR идеальной «учебной партой» для начинающих программистов. Такой взгляд имеет место быть, однако есть и аргументы «против». Микроконтроллер, в отличие от «большого» компьютера, не работает сам по себе: к нему подключается какая-то периферия, и для программирования желательно (читаем: необходимо) понимание устройства и работы этой периферии. Если мы делаем какие-то игрушки-самоделки, то нужно разбираться в электротехнике (хотя бы на уровне закона Ома), понимать принцип работы светодиодов, электродвигателей (простых и шаговых)… Не обойтись и без некоторых навыков радиомонтажа. Программист промышленных контроллеров работает обычно не один, а в команде, и может себе позволить этого всего не знать или знать поверхностно, но в промышленности на такие машины часто возлагаются задачи, для решения которых нужен серьезный математический аппарат: дифференциальное и интегральное исчисление, векторная алгебра и аналитическая геометрия… Это наука не школьного, а скорее университетского уровня.
И еще. Микроконтроллеры часто вообще не имеют средств отображения информации (мониторов и т. д.), что может существенно затруднить поиск ошибок в программах (отладку программ). В общем, микроконтроллеры не так просты, какими хотят казаться.
На мой субъективный взгляд, важнейшая привлекательная черта маломощных микроконтроллеров - это их простота, позволяющая нам за сравнительно небольшое время разобраться во всем их устройстве, так сказать, «до дна» (что совершенно немыслимо для более сложных процессоров, например для AMD64). Кроме того, эти машинки не терпят упрощенческого, наплевательского подхода к программированию. Да, мигать светодиодом с помощью такого микроконтроллера можно научиться за полчаса, но более сложная программа, сделанная наспех, без должной подготовки, без тщательного изучения предмета, может просто «не пойти», тогда как более мощные машины это прощают. Самый же весомый аргумент «ЗА» AVR для начинающих программистов: по этим машинкам имеется множество книг, в том числе написанных именно как пособия для изучения с нуля. Причем многие книги доступны для скачивания в интернете.

PIC

Это Peripheral Interface Components - очень неудачное название микроконтроллеров от американский фирмы «Микрочип»: название не только не дает представления о каких-либо особенностях изделий, но и дезинформирует нас о том, что вообще эти изделия собой представляют. Еще хуже то, что разных PICов очень много, и сколько-нибудь единого семейства они не образуют. PIC10 и PIC12 совсем примитивны и для нас едва ли интересны. PIC16 и PIC18 - вот самое то, что нам нужно: простые восьмиразрядные микроконтроллеры, по совокупности характеристик близкие к ATtiny, отличающиеся тем, что не имеют внутрипроцессорных регистров (да не очень-то они и нужны, поскольку имеется быстродействующая оперативная Storage-on-Chip). Кстати PIC18 имеют и EEPROM. Подобно ATtiny, эти микроконтроллеры не владеют операциями умножения и деления.
Если AVR сравнивать с «Жигулями», то PIC16 и PIC18 будут нашими «Запорожцами». Как учебная парта для первоклашек-промакашек пики едва ли не более удачны, чем AVR, но по ним мало толковых книг - почему-то все авторы предпочитают писать про AVR.
Микроконтроллеры PIC24 - шестнадцатиразрядные, а PIC32 - 32-разрядные. Они в нашем гараже займут место «Волг» и «Газелей».

MCS51

Это самое старое семейство восьмиразрядных микроконтроллеров, выпускаемое несколькими фирмами в разных странах мира практически с 1980-го года и с тех пор не потерявшее популярности. Эти машинки будут нашими «Москвичами»: по скорости и грузоподъемности они близки к «Жигулям» и «Волгам», но имеют несколько устаревшую архитектуру. В отличие от AVR и PIC, которые построены по принципу RISC, MCS51 имеют довольно сложную систему команд, близкую к тем, которые типичны для больших компьютеров. В частности, в «51-м семействе» имеется операция деления (ну и умножения, конечно, тоже). Если уместно говорить о скорости и грузоподъемности, то скажем так: эти машины отличаются тем, что хорошо ходят с «прицепом» - с внешней оперативной памятью, емкость которой несложно довести до 64Кбайт, и такую же величину может иметь флэш-память для программ. За много лет существования этого семейства написано немало книг, причем во всех рассматривается программирование только на языке ассемблера. Кого-то из моих читателей это может отпугнуть, я же, наоборот, рассматриваю это как преимущество.
Популяризация «51-го семейства» сдерживается тем, что для этих микроконтроллеров не выпускаются дешевые отладочные платы, подобные «Ардуинам», SEMам или Nucleo (или, по крайней мере, в сколько-нибудь широкой продаже я их не видел).

ARM Cortex

ARM расшифровывается как Advanced RISC Machine или как Acorn RISC Machine, т. е. тоже подразумевает компьютер с сокращенной системой команд, хотя, как и в предыдущем случае, «сокращенный» - скорее все-таки красивый лозунг, чем реальность.
Под этим обозначением подразумевается не какой-то конкретный компьютер, а свод рекомендаций «о том, как правильно строить компьютеры». Прежде всего оговоримся, что, согласно этим рекомендациям, правильный компьютер - 32-разрядный (хотя сейчас уже может быть и 64-разрядный), а в остальном могут быть варианты.
ARM Cortex M0…M4 - в рамках этого подкласса фирма STMicroelectronic выпускает семейство однокристалльных микроконтроллеров STM32. Это семейство большое, в нем порядка сотни членов (против примерно 30 у Atmel), и они различаются как количественно (емкостью flash-памяти и SRAM), так и «уровнем развития»:
STM32F0 (Cortex M0) - «entry-level», машины для новичков.
STM32F1 (Cortex M3) - «mainstream», машины для самого широкого круга потребителей.
STM32F2 (Cortex M3) и STM32F4 (Cortex M4) - «high-performance», машины с расширенными возможностями.
В архитектуре этих МК очевидны параллели с AVR, прежде всего гарвардская архитектура памяти (только у них нет EEPROM). Но они имеют бОльшую разрядность и тактовую частоту, и бОльшую емкость памяти (с возможностью выбора), соответственно им по плечу более трудные задачи, и такая машина прощает программисту многие грехи, которые не прошли бы на ардуинке.
На основе этих ОКМК выпускаются одноплатные микроконтроллеры: STM32 Discovery Board (буквально «плата для открытий») и NUCLEO. Обе эти серии ОПМК во многом близки и к ардуинам, и к SEM'ам, и к ним можно подключать ту же самую периферию.
ARM Cortex R - микроконтроллеры для систем реального времени - мы их касаться не будем.
ARM Cortex A - подразумевает не микроконтроллеры, а «чистые» процессоры (хотя возможно с оперативкой на кристалле - Storage on Chip - SoC), более мощные, чем STM32, и ориентированные на применение в одноплатных компьютерах общего назначения (ОПКОН). В частности, процессоры фирмы Broadcom (Cortex A53 или A7) используются в компьютерах Raspberry Pi. Orange Pi - это тоже Cortex A7, а фирма Samsung на основе своих же процессоров Exynos 4412 (Cortex A9) выпускает компьютеры Odroid. На такие компьютеры обычно устанавливают операционные системы семейства Linux.
Процессоры ARM используются в планшетах, смартфонах, роутерах, калькуляторах и т. д. Правда, не все они Cortex (соотношение между такими понятиями, как ARM и Cortex - довольно сложный вопрос. Где-то в интернете мне встретилась фраза: Cortex - самый совершенный ARM). Программное обеспечение таких аппаратов часто на 3/4 и больше состоит из запчастей от линуксов, благо такие запчасти доступны и бесплатны.
Архитектура ARM всегда подразумевает полный набор арифметических команд, в том числе для «длинных» (32-разрядных) целых чисел, а Cortex M4 и все Cortex A могут обрабатывать и числа с плавающей точкой.

AMD64

Это класс мощных 64-разрядных процессоров, как одно-, так и многоядерных. Процессор Athlon64 был выпущен фирмой Advanced Micro Devices (AMD) в 2003 году, в качестве его прототипа были использованы 32-разрядные процессоры, выпускавшиеся фирмами AMD и Intel. Athlon64 стал первым серийным 64-разрядным процессором для общегражданского применения.
Эти процессоры - тяжеловозы современного компьютерного мира, они ориентированы на большие компьютеры. Выпускаются в нестандартных корпусах и вставляются в контактный держатель - сокет (англ. socket), размещаемый на материнской плате (motherboard). На материнской плате имеются шины с чипсетом, разъемы (слоты) для оперативной памяти (она здесь сменная) и некоторые периферийные устройства: видеопроцессор, звуковую и сетевую карты, часы реального времени с резервной батарейкой. Такая плата - сложное и громоздкое устройство, изготавливаемое только в заводских условиях.
Процессоры этого типа потребляют много электроэнергии и, соответственно, любят хорошее охлаждение, чаще всего с принудительным обдувом воздухом с помощью вентилятора.
Архитектура этих процессоров представляет собой довольно замысловатое наслоение множества противоречивых технических решений, выработанных разными людьми в течение нескольких десятилетий. Разобраться в ней досконально - трудно даже для человека, систематически в течение многих лет работающего с такими процессорами. Однако неправильно было бы говорить, что начинающему программисту сюда вообще не следует соваться. Во-первых, такой компьютер чаще всего работает сам по себе, так что многие задачи можно решать, не зная никакой другой техники, кроме самого этого компьютера. Во-вторых, на таком компьютере всегда имеется операционная система: она берет на себя множество функций, которые на микроконтроллере пришлось бы осуществлять «своими вот этими руками». Например, если нужно вывести результат работы программы на экран, то программируется системный вызов, а чтобы то же самое вывести в файл, этот системный вызов нужно лишь слегка подправить. За системным вызовом стоит большая процедура, но большинство программистов ничего об этом не знает, и в этом нет ничего стыдного.
Следующий аргумент «ЗА»: на большом компьютере обычно имеется монитор, так что если в программе что-то идет не так (а поначалу ВСЕГДА что-нибудь не так), то можно, например, вывести на экран значение переменной, которая вызывает сомнения, и т. д. Таким образом, поиск неисправностей в программе значительно облегчается по сравнению с микроконтроллерами.
И еще один аргумент «ЗА», к которому следует прислушаться даже тем из вас, кто раз и навсегда решил связать свою жизнь с «мелкокаменной» техникой (микропроцессорами и микроконтроллерами). Разработка ПО для такой техники в наше время никогда не ведется на самой той машине, для которой программа предназначена: работа ведется на инструментальной машине, в роли которой практически всегда выступает компьютер AMD64.
Иными словами, мощный и сложный по своему внутреннему устройству компьютер представляет программисту целый ряд удобств, благодаря которым работа на нем едва ли не проще, чем на микроконтроллерах.
Кроме того, именно на «больших» компьютерах строятся почтовые и WEB-серверы, для которых создается огромное количество программ на специализированных языках (типа PHP, Java и Java-script) - это целый большой пласт в искусстве программирования. Не менее 3/4 всех программистов, работающих в мире, занято именно в этой отрасли, так что знакомство с такими компьютерами совершенно необходимо.

Понятие ресурсов компьютера

Ресурс - это нечто, что все хотят, но на всех не хватает. Каждый человек хочет иметь такой компьютер, который мгновенно решал бы самую сложную задачу. Но такой компьютер будет чудовищно дорог. Компьютер, который мы покупаем за имеющиеся у нас деньги, неизбежно будет иметь ограниченные «умственные способности». Любая причина, которая их ограничивает, и есть ресурс.
На сегодня мы ограничимся рассмотрением двух ресурсов: процессорного времени (или производительности процессора - можно смотреть и так и так) и объема памяти.
Мы получили представление о том, какой компьютер мощный, какой маломощный. Теперь мы можем сказать, что мощный компьютер имеет большие ресурсы, а маломощный - маленькие.

Математическое обеспечение

В предыдущей главе мы коснулись аппаратного обеспечения компьютеров, в повседневной речи - «железа». Соответствующий английский термин - hardware - буквально «твердое имущество». Теперь остановимся на «мягком имуществе» (software). Этим термином называют математическое обеспечение компьютеров.
Если говорить о математическом обеспечении, то обычно прежде всего люди представляют себе программы. Давайте с самого начала договоримся, что собственно программы - это верхушка айсберга. Сам же айсберг на 90% состоит из чистой математики.
Математика - наука об общих количественных закономерностях в окружающем нас мире. Она выросла из того, что люди, по мере своего умственного развития, стали замечать, что очень разные на первый взгляд события могут быть представлены одними и теми же числовыми и логическими описаниями, а значит и разные задачи могут быть решены сходными методами.
Чтобы понять, что такое математическая модель, возьмем для примера две задачи.
ЗАДАЧА 1. У нас имеется три железорудных карьера, каждый из которых может выдавать в день некоторое количество руды (свое), и два металлургических завода, каждый из которых может принимать в день некоторое количество руды. Между карьерами и заводами проложены дороги, расстояния между пунктами известны. Сколько самосвалов нам потребуется для доставки всего имеющегося количества руды с карьеров на заводы?
Как такую задачу вообще решать, сразу и не сообразишь, да? Однако с первого взгляда ясно, что можно решить «в лоб», т. е. пусть вся руда с карьера N1 поступает на завод N1, и т. д. Но насколько удачно будет такое решение? Если бы у нас были совсем маленькие карьеры и один-единственный грузовик, мы могли бы придумать несколько вариантов организации перевозок и выбрали бы из них такой, чтобы наш грузовик хотя бы «накручивал» за день меньше километров. Если же ясно, что одним грузовиком не обойтись (а возможно их потребуются сотни или тысячи), то вариантов будет так много, что перебирать их будем до скончания века, а на заводах и в карьерах нашего решения просто не дождутся… Значит, хотелось бы иметь математический метод, который бы позволил нам получить оптимальное расписание движения грузовиков без перебора всех вариантов.
ЗАДАЧА 2. У нас имеется ракетный крейсер, вооруженный 16 ракетами «Базальт», и подводная лодка с 16 ракетами «Гранит», а нам противостоит вражеский флот в составе 2 авианосцев, 3 крейсеров и 10 эсминцев. Мы знаем, что таким количеством ракет все корабли нам не потопить, но если сосредоточить весь ракетный залп по одному авианосцу, то его мы потопим наверняка. Аналогично можно потопить 2 крейсера или 5 эсминцев. Если это удастся, будем сверлить дырочки под ордена, да? Но как поступит после этого командир вражеского флота? Подсчитает потери, доложит вышестоящему командованию… Сообразит, что больше ракет у нас нет, и с оставшимися силами пойдет дальше бомбить наши города? А у нас там дети…
Очевидно, что эту задачу, как и предыдущую, не следует пытаться решать «в лоб». Может быть, распределить наши ракеты на вражеские корабли так, чтобы, допустим, не топить их, а нанести такие повреждения, после которых противнику уж точно ничего не останется, как убираться восвояси? Но если так, то сколько ракет нацелить на каждый вражеский корабль?
Итак, две казалось бы очень разных задачи из разных отраслей человеческой деятельности. Но вот что интересно: решаются они практически одним и тем же методом. Называется он динамическое планирование или динамическое программирование. Слово программирование здесь не должно вводить нас в заблуждение (просто этот метод был изобретен достаточно давно, когда программирование в «нашем» понимании этого слова еще не было в ходу).
К чему я это все? К тому, что программирование (в «нашем» смысле этого слова) начинать следует не с того, чтобы очертя голову хвататься за клавиатуру, а с изучения математики.
Теория вероятности… «Наука о случайном? Как такое может быть?»
Теория игр… «Что мы, дети что ли, чтобы играть?»
Теория игр - это вам не в бирюльки играть, это абсолютно взрослая наука, изучаемая в институтах! Учебник «Введение в теорию вероятности и ее приложения» состоит из нескольких томов, и весьма толстых (а теория игр - это как раз одно из этих приложений). А используются эти приложения людьми самых разных профессий, от финансистов до военных.

Об алгоритмах и программах

Слово алгоритм восходит к имени математика Аль-Хорезми, однако связь между ними не совсем понятна. Первоначально алгоритмом назывался метод вычисления («извлечения») квадратного корня из заданного числа. В то время, когда жил и работал Аль-Хорезми, для математиков уже не были новостью формулы и расчеты по ним. Проблема в том, что квадратный (или иной) корень невозможно вычислить по фомуле. То есть формула-то имеется (она была предложена Героном еще задолго до Аль-Хорезми), но если произвести одно вычисление по этой формуле, то получим лишь грубо приблизительный результат: например корень из 2 - якобы 1.5 (на самом деле 1.41…), а корень из 3 - якобы 2 (на самом деле 1.73…). Чтобы получить более точный результат, расчет по формуле нужно выполнить несколько раз с поправкой исходных данных на каждом шаге. Этот метод, собственно, и был назван алгоритмом, а затем этим словом стали называть любые указания по выполнению умственных операций, по которым нужные действия следует выполнять в строго определенной последовательности.
Рассмотрим алгоритм Герона (ну не придумать более красивой формулировки!) подробно.
Если X - число, из которого извлекаем корень, а Y - значение функции, то Y = SQRT (X), где SQRT (от английского SQuare Root) - имя функции, принятое во всех нормальных языках программирования. Начальное, грубо приблизительное значение результата:
Y0 = (X+1)/2
Теперь, собственно, формула Герона:
Yn+1 = (Yn + X/Yn)/2
Откуда возникла эта формула? - очевидно, из самого смысла квадратного корня: корень - это не что иное как длина стороны квадрата, площадь которого равна X. Такую же площадь имеет прямоугольник, одна сторона которого X, а другая 1. Этот прямоугольник мы как бы последовательно трансформируем в прямоугольники такой же площади, с меньшей длиной, но большей шириной, и в конце концов получаем такой прямоугольник, у которого длина и ширина практически равны, т. е. квадрат.
Пример. Пусть X=2.25, тогда ожидаем Y=1.5. Y0=(2.25+1)/2=1.625. Подробные вычисления опускаю, привожу промежуточные результаты: Y1=1.5048, Y2=1.50000766… - получаем ряд значений, каждое следующее из которых ближе к искомому, чем предыдущее. Останавливаемся, когда очередное значение Yn+1 отличается от предыдущего Yn на величину не более заданной погрешности. Например, если мы хотим получить значение корня с точностью до 0.001, то в нашем примере такая точность уже достигнута.
Этот старый алгоритм называется итерационным, или алгоритмом последовательного приближения. Именно его нам в институте давали на лекциях по программированию. Я в своем курсовом проекте (том самом, на ЕС-1060) применял этот алгоритм, модифицированный для извлечения кубического корня. Наши горе-профессора, похоже, не догадывались, что этот алгоритм несовершенен и что в реальной вычислительной технике от него давно отказались. Что же не так в этом алгоритме?
Во-первых, в рассмотренном примере мы ожидали увидеть точный результат 1.5, а получили результат с погрешностью, и нетрудно убедиться, что для любого другого значения X результат тоже будет с погрешностью. В каких-то ситуациях это приемлемо, а в каких-то нет. Во-вторых, в нашем примере мы произвели всего 3 итерации и получили удовлетворяющий нас результат, но так будет не всегда: при больших значениях X количество итераций будет возрастать. В-третьих, на каждой итерации приходится выполнять две операции деления, а деление может занимать больше времени, чем любая другая арифметическая операция. Правда, если мы извлекаем именно квадратный корень, то одно из этих делений будет на 2, а это как раз такая операция, которую можно сделать по-хитрому. Но не на всяком языке возможно запрограммировать деление на 2 по-хитрому…
Исходя из этих соображений, в реальной компьютерной технике итерационный алгоритм извлечения квадратного корня заменен более прогрессивным алгоритмом поразрядного взвешивания (он же алгоритм «цифра за цифрой»).
Чтобы понять суть алгоритма поразрядного взвешивания, вспомним, как производится обычное взвешивание на весах с двумя чашами. У нас имеется набор из N гирь: «старшая» - самая тяжелая, а каждая следующая - в два раза легче предыдущей. Соответственно нам потребуется произвести N взвешиваний: ставим на весы очередную гирю, начиная со «старшей», и смотрим результат. Если весы пришли в равновесие, значит имеем ТОЧНЫЙ результат, и на этом останавливаемся. Если гиря перевешивает, ее убираем. Затем пробуем следующую по порядку гирю, и т. д. Правда, для того, чтобы вычислить квадратный корень, весы нам потребуются хитрые: они должны сравнивать вес «товара» с КВАДРАТОМ веса гирь. В природе таких весов не существует, но мы их смоделируем на компьютере: операция взвешивания сводится к возведению веса гирь в квадрат, т. е. к умножению его на самого себя. По итогам N взвешиваний будем иметь некоторые гири на чаше весов - соответствующие биты в ячейке, отведенной под результат, будут установлены в 1, другие гири «отдыхают» - соответствующие биты ставим в 0. Общее число верных цифр (бит) результата, очевидно, равно числу произведенных взвешиваний, если только точный результат не был получен досрочно.
Оба рассмотренных алгоритма вычисляют корень путем повторения одних и тех же действий несколько раз с изменением исходных величин. Повторение - значит затраты времени. Но при поразрядном взвешивании нам потребуется повторять, по большому счету, единственную операцию умножения (возводим пробный результат в квадрат) столько раз, сколько точных цифр мы хотим иметь в результате. Если нас устроит приблизительный результат, то и получим его быстро, если же требуется высокая точность, придется проявить упорство. Затраты времени зависят только от требуемой точности, но не от величины подкоренного числа.
Поразрядное взвешивание - это, строго говоря, не самостоятельный законченный алгоритм, а скорее принцип, «скелет алгоритма», который может обрасти различным «мясом». Он позволяет вычислить любую функцию, даже самую зловредную, если имеется «хорошая» функция, обратная искомой. Допустим, имеем функцию Y=Fbad(X), которую мы вообще не знаем как вычислить (вообразим, что мы ничего не знаем про итерационный алгоритм). Зато у нас имеется функция X=Fgood(Y), которую мы как минимум МОЖЕМ вычислить. Тогда поразрядное взвешивание будет для нас беспроигрышным решением для вычисления Fbad. А если еще окажется, что Fgood достаточно проста для вычисления (возведение в квадрат реализуется ОЧЕНЬ ПРОСТО путем умножения числа само на себя), то это серьезный аргумент в пользу того, чтобы поразрядное взвешивание предпочесть любым другим методам. И где бы этот метод ни применялся, всегда он сохраняет основное преимущество: позволяет экономить время при приблизительных расчетах.
К чему я это все? К тому, что математика за тысячелетие своего существования выработала множество путей решения разных задач (сколько методов доказательства теоремы Пифагора вы знаете?) И вот тут мы приходим к пониманию одной очень важной мысли, которую вы едва ли найдете в книгах по компьютерной технике и уж точно не услышите ни от школьных учителей, ни от университетских преподавателей - ну так послушайте старшего товарища и сделайте наоборот!
Мысль такая: если алгоритм - это чистая наука, математическая абстракция, то программа - это техническое устройство, промышленное изделие. Программа проектируется инженерами, ее создание характеризуется конечной трудоемкостью, а будучи изготовлена, программа эксплуатируется как инструмент для решения некоторой задачи, которую ставит перед нами жизнь. У математиков, как и у представителей других научных специальностей, изобретение\открытие любого метода решения какой-либо задачи (пусть этот метод хоть 51-й по счету) считается достижением. Там не принято рассуждать, что такой-то метод хорош или плох. В лучшем случае что-нибудь скажут на тему изящества, поаплодируют на каком-нибудь симпозиуме… Но инженерная деятельность отличается от науки, а программирование - это инженерная деятельность. И к нашему изделию предъявляются требования: оно должно быть хорошим инструментом. Это не значит, что рассуждения об изяществе к нему неприменимы. Есть же определенная красота в старинных стамесках и рубанках, а над современными бензопилами работают специалисты-дизайнеры. Да, все так, но если есть два алгоритма А и Б и один из них имеет недостатки, а другой преимущества, то для технического изделия однозначно следует предпочесть тот, который имеет преимущества. А чтобы предпочесть, нужно, как минимум, знать о существовании такого алгоритма, а как максимум - желать и уметь сравнить алгоритмы и понять, как они соотносятся между собой. А сделать хорошее изделие на основе несовершенного алгоритма - это вряд ли.

Лирико-математическое отступление

А сейчас давайте запасемся листком бумаги и карандашом, залезем с ногами в шезлонг в саду под яблоней, или осенним дождливым вечером в кресло у растопленного камина, или зимней непроглядной ночью на верхнюю боковую полку плацкартного вагона… это кому как нравится. Нет, писать программу сейчас не будем: нам для этого пока что не хватает теоретической подготовки. Сегодня мы ЧИСТО МЫСЛЕННО, без всякого компьютера (но с оглядкой на компьютер!), попробуем что-нибудь вычислить. Квадратный корень извлекать мы худо-бедно научились, теперь надо придумать что-нибудь позаковыристее. Простая задача: вычислить логарифм.
– Фиу, ну ты сказал: простая! Как его вычислить-то?
– В наше время логарифмы изучали в школе, в рамках «элементарной» математики, но там мы не касались вычисления их значений. В институте, в рамках курса высшей математики, преподается теория рядов. Получается, что многие хитрые математические функции можно «разложить в ряд Тейлора». Например, синус и косинус можно вычислить так:

(здесь будет формула для синуса и косинуса)

Ряды бесконечны, а в компьютерной технике ничего бесконечного, как мы знаем, не бывает. Поэтому подобные вычисления всегда выполняются с той или иной приблизительностью. Получая задание на разработку алгоритма, мы всегда требуем оговорить допуск, т. е. величину погрешности, которая приемлема для решения данной задачи.
Ряды для синуса и косинуса - знакопеременные. Для таких рядов из теории известно (да это, в принципе, и невооруженным глазом видно), что для любого числа N, если вычислить сумму N первых членов и сумму N+1 первых членов, то эти два значения будут границами, а искомое значение суммы бесконечного ряда будет где-то между ними. Иными словами, если N-ный член ряда меньше оговоренного допуска на погрешность (а каждый следующий член ряда меньше предыдущего, так что такой маленький член нам рано или поздно встретится), то на этом можно остановиться: требуемая точность вычисления результата достигнута.
Ряд для натурального логарифма будет вот такой:

(здесь будет формула для логарифма)

Эту формулу нам пишут на доске преподаватели математики, для которых эта формула - чистая наука, абстракция. А преподаватели программирования не хотят думать о сложном в жизни и советуют нам вычислять функции так, как учат на высшей математике… Если у такого препода спросить: «Зачем ты учишь молодежь плохому?», то ответ будет: «Да какая разница? Мы же не боевую программу пишем, пусть учатся просто составлять алгоритм по формулам». Я с такой позицией согласиться не могу. Как я уже говорил выше (и не устану повторять), молодежь надо учить не просто переводу алгоритмов на язык программирования, а надо учить МЫШЛЕНИЮ.
Этот ряд вообще по жизни сходится достаточно медленно. Он не знакопеременный, значит то, что было только что сказано про синус и косинус, к логарифму не относится. Если мы будем считать этот ряд член за членом, то сумма будет все время возрастать. Сколько бы членов мы ни считали, все равно останется бесконечное количество. В один прекрасный момент очередной член окажется меньше самого младшего разряда нашего машинного слова. Как говорят специалисты: «выйдет за пределы разрядной сетки». На этом вычисления волей-неволей придется остановить, потому что результат просто не будет меняться. Каждый следующий член ряда будет еще меньше, но их много - чему будет равна их сумма? Остается только гадать. А это значит, что у нас нет вообще никаких гарантий, что в полученном результате есть хоть сколько-то верных значащих цифр! Чем это может быть чревато, например, при запуске ракеты - догадайтесь сами.
Допустим, у нас под рукой мощный компьютер, способный «переваривать» 80-разрядные числа с плавающей точкой. Суммирование ряда на такой машине будет однозначно не быстрым… Но спасет ли это нас?
К чему мы пришли? Похоже, к тому, что разложение в ряд, которое так хорошо работало для синуса, применительно к логарифму более чем просто неудачно и некрасиво - это НЕГОДНЫЙ МЕТОД. С точки зрения чистой науки негодных методов не бывает: у нас есть формула Тейлора - значит надо ее рассмотреть на уроках. Но программирование - это не совсем наука - это инженерная работа…
Вдумчивый читатель, вероятно, уже догадывается, к чему я клоню: к поразрядному взвешиванию. Ага, наш логарифм - это Fbad, а обратная ему функция Fgood - это возведение в степень. Великолепно! Напишем на языке типа Фортрана:

TRY=ЕXP(GIRI)

где TRY - пробное значение, которое мы хотим подогнать к исходному числу, ЕXP - возведение числа Непера (e) в степень, GIRI - общий «вес гирь на чаше весов». Так может написать студент-программист, и пожалуй, даже может получить за это хорошую оценку… Но давайте как следует подумаем, что мы написали. Операция возведения в степень - довольно паршивая: медленная. А при поразрядном взвешивании ее придется повторять энное количество раз. В общем, красиво не получается.
А теперь давайте для полноты картины представим, что 80-разрядного компьютера с плавающей точкой у нас нет, а есть примитивный 8-разрядный микроконтроллер, и все это нужно сделать на нем.
– Да пОлно, какой микроконтроллер??? Сам только что говорил о 80-разрядных числах с плавающей точкой!
– Говорил. Я вам также говорил, что в учебнике высшей математики описаны задачи, для решения которых 80-разрядные числа действительно необходимы. Но это не наш сегодняшний случай: вычисление логарифмов к таким задачам не относится. Сегодня я вам намерен показать, что подобные (и многие другие) вычисления вполне по плечу даже самым примитивным машинкам.
Где в микроконтроллерной технике могут потребоваться логарифмы? Например там, где мы имеем дело с радиоволнами или со звуком. Логарифмические шкалы часто используют для настройки радиоприемников и радиопередатчиков. Измерение затухания радиоволн или интенсивности звуков также часто сопряжены с логарифмированием. Всевозможные стенды для испытания радиотехнических и акустических приборов очень здОрово строятся на микроконтроллерах, так что логарифмирование может потребоваться. Скажу больше: поскольку человеческие глаза и уши имеют логарифмическую чувствительность, то любые измерения-испытания, связанные с видимостью/слышимостью различных предметов для человека, без логарифмирования не обходятся.
Для упрощения задачи (мы ведь сейчас не пишем боевую программу - мы просто теоретизируем!) давайте считать, что исходное число, от которого берем логарифм, - натуральное, в размере одного байта. Это абсолютно типичная ситуация для микроконтроллерной техники (допустим, мы хотим измерить уровень звука, поступающего на микрофон). Логарифм мы выше рассматривали натуральный, а теперь будем брать двоичный:
Y=LB(X)=LOG2(X)
Нам ведь, по большому счету, все равно, какой логарифм вычислять: все логарифмы пропорциональны. Если нам скажут: «хотим стопроцентный натуральный», просто умножим полученное нами значение двоичного логарифма на известный коэффициент. Значение логарифма будем представлять как число с фиксированной точкой: по одному байту на целую часть и на дробную. Логарифм нуля не существует, логарифм 1 - всегда 0, логарифм 2 - равен 1… Все значения логарифма будут положительные, и это нам на руку. Целую часть двоичного логарифма найти очень легко: нужно найти в исходном числе старшую единицу, определить, в каком она разряде, и из номера разряда вычесть 1. Пример. Пусть исходное число 61=0b00111101 - старшая единица в шестом разряде, значит целая часть логарифма 5 (запись типа 0b00111101 всем, надеюсь, уже понятна?). Проверим: 25=32 и это меньше нашего исходного числа, а 26=64 - это перелет, значит целая часть логарифма меньше 6, то есть все-таки 5. Итак, целая часть требуемого результата определяется легко, и мы сейчас про нее забудем: она нам неинтересна. Сосредоточимся на дробной части. Если уж говорить совсем строго, то вычисление логарифма ВСЕГДА следует разделить на две части: вычислив целую часть, сразу от нее избавиться, чтобы осталась мантисса исходного числа (находящаяся в интервале значений 1⇐Mx<2 и отличающаяся от исходного числа X на целую степень двух), а для нее уже отдельным этапом вычислить мантиссу логарифма, величина которой будет в диапазоне от 0⇐My<1 - это уже совершенно нормальное дробное число с фиксированной точкой.
Мы договорились, что логарифм - это Fbad(X)=LB(X), а обратная ему функция Fgood(X)=2X. То есть при каждом взвешивании нам нужно вычислить 2GIRI, где GIRI - опять-таки «общий вес гирь». Очередная гиря, которую мы сейчас должны поставить на весы, - 0.5, при следующем взвешивании будет 0.25 и т. д. Но как возвести 2 в дробную степень? Допустим, на некотором этапе нам потребуется вычислить 20.75 - что это вообще такое, с чем его едят?
Очевидно, 20.75 = 20.5 * 20.25
А что такое 20.5? Очевидно это квадратный корень из 2 - его мы уже умеем вычислять. А 20.25 - это 21/4, т. е. корень четвертой степени из 2, он же корень из корня из 2…
Поскольку от нас требуют мантиссу логарифма размером в байт, то всего нам нужно будет выполнить 8 «взвешиваний», и понадобится для этого 8 «гирь». Но, в отличие от того как было при извлечении корня, гири потребуются хитрые: самая большая будет иметь «вес», равный корню из 2, и остальные соответственно тому что было сказано выше. Все они будут иметь целую часть, равную 1, и дробную - каждая свою. Веса гирь запишем в табличку из 8 ячеек, причем целую единицу в эту табличку можно не записывать - надо будет только не забыть ее при умножении, а в табличку занести только дробные части. Тогда для ее хранения нам потребуется всего 8 байт - мизер даже для совсем простого микроконтроллера. Вычислить эти значения нужно будет один раз при запуске нашей программы. А поскольку мы говорим о микроконтроллере, то в нашем распоряжении имеется EEPROM - почему бы не вычислить все «веса гирь» один раз с использованием вспомогательной программы и «зашить» их в EEPROM раз и навсегда?
Ради спортивного интереса я вычислил значения первых шести «гирь» (только дробные части!). Запишем как на языке ассемблера:

VESGIRI db 0b01101010     ;2 ^ 1/2 = корень из 2
        db 0b00110000     ;2 ^ 1/4 = 0.189
        db 0b00010111     ;2 ^ 1/8 = 0.0905
        db 0b00001011     ;2 ^ 1/16 = 0.0443
        db 0b00000101     ;2 ^ 1/32 = 0.0219
        db 0b00000011     ;2 ^ 1/64 = 0.0109

Обратите внимание на знак ^ - это символ возведения в степень, который используется в некоторых языках программирования (хотя и не во всех, но всем действующим программистам он понятен). Здесь я использую этот знак в комментариях, т. е. только для того, чтобы программа была понятна людям.
При «взвешивании» веса гирь придется не складывать, а умножать. В природе таких весов не существует, но мы можем их смоделировать. Разумеется, мы не будем при каждом «взвешивании» перемножать веса всех гирь - будем умножать результат предыдущего взвешивания на вес той гири, которую ставим сейчас на весы. Здесь есть подвох: «младшие» гири будут иметь очень маленький вес, не выражающийся в масштабе одного байта, да их еще придется умножать, а если умножать маленькое на маленькое, получим совсем маленькое… Значит полный байт дробной части логарифма мы не получим, придется или торговаться насчет допуска, или хранить каждую «гирю» не в одном байте, а в двух. Сам же процесс взвешивания сводится, по большому счету, к единственной операции умножения. Правда, умножение чисел с дробной частью на 8-разрядной машинке, особенно если умножать двухбайтные числа - задача нетривиальная. Готовой команды для этого мы не найдем (а есть микроконтроллеры, которые вообще умножением не владеют), значит нам придется написать процедуру из нескольких команд, возможно около 10. Надеюсь вы мне поверите, что ничего сверхъестественного в этом нет, хотя в принципе это серьезный повод задуматься о микроконтроллере с бОльшей разрядностью.

Какие бывают программы

Программы прикладные и системные. Прикладная программа (в переводных с английского книгах часто называемая просто приложением, англ. application) предназначена для решения некоторой жизненной задачи (деловой или игровой). Однако такие программы для больших компьютеров строятся так, что не могут обойтись без системных программ, которые обеспечивают им «комфортную среду обитания». Соотношение между прикладными и системными программами примерно такое же, как на железной дороге соотношение между вагонами и паровозами.
Операционная система (ОС) - программная принадлежность больших компьютеров, оправдывающая свое существование тем, что обеспечивает «уживаемость» множества прикладных программ на одном компьютере. Минимальный набор функций ОС:
* Организация хранения различных программ, данных для их решения и результатов их работы в виде файловой системы;
* Прием от пользователя команд на решение нужных ему задач и вызов соответствующих программ для выполнения этих команд;
* Организация попеременного, а при необходимости и одновременного выполнения различных программ с распределением ресурсов и устранением конфликтов;
* Обеспечение программам доступа к устройствам ввода-вывода;
* Установка и удаление прикладных программ.
«Большой» компьютер немыслим без операционной системы, но на микроконтроллерах ее обычно нет.
Операционная система представляет собой сложное техническое изделие в составе следующих важнейших частей.
Ядро ОС - это наш системный шеф-повар, который постоянно находится на своей «кухне» в полностью или почти невидимом для нас состоянии, владеет определенными профессиональными секретами, знает, где у него мясо и где картошка, где кастрюли и где плита, и выполняет наши заказы, которые мы ему передаем в виде системных вызовов.
Оболочка - это наш системный официант, которому мы непосредственно отдаем «заказы» (команды). Название «оболочка» отражает тот факт, что пользователь не имеет дела непосредственно с ядром, а имеет дело с оболочкой, и ядро как бы скрыто от пользователя оболочкой.
Утилиты (англ. utilities - «полезняшки») - инструмент и принадлежности, которые не решают прикладных задач, но порой требуются для обслуживания компьютера: настройки и модернизации, поиска и устранения неисправностей, обзора обстановки и т. д. Подобно тому как грамотный автомобилист всегда возит с собой домкрат, насос, буксирный «галстук» и бензошланг, как на корабле обязательно есть шлюпки, спасательные круги, пластырь для заделки пробоин и куча всякого другого аварийно-спасательного имущества, так и грамотный компьютерщик имеет под рукой определенный набор утилит и при необходимости пользуется ими. В наше время каждая операционная система комплектуется «джентльменским набором» утилит, число которых может составлять сотни.
Операционная система - сложное программное изделие. На больших компьютерах она «съедает» определенную долю ресурсов компьютера, прежде всего место в памяти и часть времени работы процессора. Современные операционные системы чаще всего весьма «прожорливы» и могут работать только на мощных компьютерах. Если попытаться эксплуатировать такую систему на компьютере с недостаточными ресурсами, то она будет, как минимум, «тормозить», неизбежно заставляя тормозить и прикладные программы, а в худшем случае вообще не сможет работать.
На микроконтроллерах операционных систем чаще всего не бывает. В лучшем случае на микроконтроллере имеется загрузчик - небольшая программа, «зашитая» во flash-память и решающая только одну задачу из тех, которые выше упомянуты для операционной системы: установку прикладной программы. Микроконтроллер и его загрузчик строятся так, чтобы выполнять одну-единственную прикладную программу многократно в течение длительного времени (иногда - в течение всего срока службы микроконтроллера). Прикладная программа для микроконтроллера называется скетч (англ. sketch - рисунок, набросок). Скетч пишется так, как будто ему доступны все ресурсы машины, но и на помощь операционной системы, например при вводе-выводе данных на периферийные устройства, расссчитывать не приходится.
Операция по установке скетча на микроконтроллер с помощью загрузчика называется зашивкой или заливкой.
Прошивки - это программные принадлежности к тем или иным компьютерным «железякам», обеспечивающие им хотя бы минимальную «жизнеспособность». Чаще всего прошивка «зашивается» в свое устройство на заводе-изготовителе намертво (или с возможностью замены (перепрошивки) при необходимости), но бывают и такие внешние устройства, которые в магазинах продаются без прошивок, а прошивку приходится приобретать отдельно (обычно искать в интернете и скачивать). Это типично, например, для устройств Wi-Fi.
Специализированные компьютеры (например, медицинские диагностические приборы) и сложные гаджеты типа навигашек имеют объемистые и функционально богатые прошивки, по принципу действия похожие на операционные системы, а зачастую и состоящие на 3/4 и более из «запчастей» от операционных систем. Чем такая прошивка отличается от полнопрофильной ОС? По большому счету, отсутствием функции установки прикладных программ.
Слово «прошивка» используется в двух значениях: как собственно программное изделие и как техническая операция по размещению этого программного изделия в памяти устройства (прибора, аппарата), для которого оно предназначено.
Драйвер - программа, обеспечивающая различным системным и прикладным программам работу с некоторым устройством ввода-вывода. Что общего между прошивками и драйверами и в чем различие?
Прошивка - принадлежность конкретного «железа» и не относится к какой-либо операционной системе, а драйвер является составной частью операционной системы и не может применяться в других ОС. Прошивка в процессе работы устройства находится в нем, в худшем случае ее приходится загружать в устройство один раз при его включении (подключении), после чего про нее можно забыть. Драйвер находится в оперативной памяти компьютера в постоянной готовности к действию, и каждое обращение прикладной программы к устройству производится путем вызова драйвера.
Написание прошивок и драйверов считается (и справедливо) одной из самых сложных работ в программировании, поскольку требует детального понимания работы данной конкретной «железки».
Средства разработки, среда разработки (англ. development environment) - программный инструментарий, близкий к утилитам и обеспечивающий пользователю возможность самостоятельно создавать новые программы.

Технология и инструмент в работе программиста

Определимся со значениями слова программирование. У этого слова значений несколько. Выше мы рассмотрели динамическое программирование как математический метод решения определенных задач. Другое значение: программирование - процесс написания, или точнее разработки, программ. Этим же словом называется и техническая операция закладки (установки, «заливки») программы в компьютер или иное устройство. Для этой операции в ряде случаев нужен специальный прибор - программатор.
Мы уже говорили (и я не устану повторять), что программирование - не развлечение, а инженерная работа. А инженерная работа должна выполняться по технологии.
Технология - совокупность знаний (в т. ч. изложенных в тех или иных нормативных документах) о том, как выполнить некоторую работу с наименьшими затратами и высоким качеством.
Работа по технологии обычно предполагает использование определенного инструмента.
Когда появились первые ЭВМ и программирование только начиналось, программисты составляли непосредственно машинный код. Это так и называлось: программирование в кодах. Программа при такой технологии получалась практически нечитаемой для человека. Это казалось приемлемым, пока создается новая программа «с нуля». Однако по мере развития отрасли все чаще возникала необходимость что-то изменить в программе, написанной когда-то давно (и возможно, другим человеком). Очевидно, это была задача абсолютно нереальная. Так народ пришел к решению писать не сразу машинный код, а исходный текст, который может быть прочитан человеком и, с другой стороны, может быть преобразован в машинный код по известному алгоритму. Первоначально это преобразование приходилось делать вручную. Я еще застал те времена (1980-е годы), когда программирование для микропроцессора серии К580 (единственного доступного нам тогда настоящего микропроцессора) приходилось выполнять именно по такой первобытной технологии. Однако уже в 1950-е годы пришло понимание того, что это преобразование представляет собой абсолютно машинную работу и должно выполняться не человеком, а самим компьютером по программе, которую назвали транслятором (англ. translator - переводчик). Трансляторы бывают: ассемблеры и компиляторы, а также существуют близкие к ним программы - интерпретаторы.
Ниже мы рассмотрим, в чем отличие ассемблера от компилятора, а сейчас остановимся на интерпретаторе. Это программа, которая читает некоторый текст (иногда называемый сценарием или скриптом, англ. script), трактует его содержимое как команды и тут же, «не отходя от кассы», выполняет эти команды. Скрипт может быть написан на любом языке, лишь бы существовал интерпретатор, способный понимать такой язык.
Интерпретатор выполняет команды «на лету», ему нет необходимости записывать их в какой-либо отдельный текст. В отличие от него транслятор, встретив в исходном тексте команду, не выполняет ее сразу, а записывает в свой выходной текст, который, скорее всего, будет нечитаем для человека, а близок к машинному коду. Собственно, в первые десятилетия существования компьютеров это и был самый настоящий машинный код, который можно было сразу или потом загрузить в оперативную память и запустить на выполнение. Пока программы были не очень сложные, такая технология всех устраивала.
Разработка и эксплуатация программ в интерпретационном режиме предполагает существование скрипта и интерпретатора на одном компьютере. Если задачу нужно перенести на другой компьютер, то переносить придется как скрипт, так и интерпретатор. Использование транслятора предполагает разработку программы на том самом компьютере, на котором она будет выполняться, с возможностью передачи готового машинного кода на другие компьютеры. Поскольку машинный код нечитаем для человека, люди, работающие на других компьютерах, будут вынуждены использовать этот машинный код «как есть», без возможности что-то изменить в программе. Для изменений им придется как-то приобретать исходный текст.
Что нам нужно для написания исходного текста? - Очевидно текстовый редактор. В современном компьютерном мире разнообразие текстовых редакторов не поддается счету. Какой выбрать? По большому счету - любой, какой нравится. Только не Word, потому что транслятор требует от нас «чистый» текст, а Word для этого не предназначен - он выдает текст со служебной информацией (о виде и размере шрифта и т. д.) - такой формат представления текста для наших целей непригоден.
По мере развития отрасли программы усложнялись, их исходные тексты росли в объеме. Например, АСУ производством, которую я когда-то разрабатывал, занимала порядка сотни печатных листов. Внесение поправок в такой текст довольно неудобно и хлопотно, поэтому исходные тексты («исходники») больших программ делятся на части, которые могут разрабатываться разными людьми и даже на разных языках (при интерпретационной технологии такая возможность обычно не предусматривается). Каждый файл с исходным текстом транслируется отдельно, в результате образуется соответствующий кусок машинного кода, который снабжается определенной служебной информацией - и получается объектный код. Объектный код составляется в определенном стандарте, принятом в данной операционной системе. Этот стандарт обязателен для любых трансляторов с любых языков.
Построитель задачи - программа, которая «сшивает» машинную программу из множества кусков объектного кода. Обиходное название построителя задач - линкер или линковщик (англ. link - под этим именем эта программа фигурирует практически во всех операционных системах).
«Ее знали только по имени…» - речь идет о программе make. Едва ли кто-нибудь может дать определение, что собой представляет эта программа. Просто make. Это имя известно программистам, работающим (работавшим) в DOS и OS/2, MacOS и UNIX, Linux и Windows. Эта программа - бригадир над трансляторами и линковщиками. Чтобы собрать готовый машинный код из исходных текстов, программист не обращается напрямую к этим программам, а дает просто команду make.
Мы рассмотрели технологию разработки программ, типичную для больших компьютеров. Основная идея этой технологии: для разработки программ можно использовать любой компьютер - нужно лишь оснастить его вышеперечисленным инструментарием. Программы для микроконтроллеров разрабатываются несколько иначе. Микроконтроллер чаще всего не имеет ни монитора, ни клавиатуры, ни текстовых редакторов, без которых написание исходных текстов немыслимо. К тому же приличный транслятор - достаточно сложная программа: микроконтроллеру может просто не хватить емкости памяти для работы транслятора. Поэтому разработка программ для микроконтроллеров обычно выполняется на больших компьютерах, тем более что на них обычно имеется весь необходимый инструментарий. Если же большой компьютер программно несовместим с микроконтроллером, то приходится создавать специальные инструменты: кросс-ассемблеры и кросс-компиляторы. Иногда в программировании микроконтроллеров используют симуляторы и эмуляторы - программы, «играющие роль» микроконтроллера, изображающие его действие на большом компьютере. В ряде случаев они позволяют создать программу для микроконтроллера в отсутствие его самого, но ждать чудес от таких программ я все-таки никому не советую.
Development environment - «среда разработки» - не что иное как набор вышеперечисленных программ, хорошо «подогнанных» друг к другу, создаваемый одной фирмой и поставляемый на рынок как единое изделие. Иногда используется термин Integrated Development environment - IDE (не путать с одним из типов винчестеров IDE!). Например для микроконтроллеров Atmel известна среда разработки AVRStudio, хотя Ю. Ревич в своей книге «Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера» критикует этот продукт.

Языки программирования: язык ассемблера и языки высокого уровня

Программирование в кодах было дело хотя и нудное и трудоемкое, но имело важное преимущество: программист «держал в руках» каждую команду, что позволяло создавать продукт высокого качества: не допускать лишних действий, оптимизировать «тормозные» места, засекать и устранять аварийные ситуации… Чтобы не терять это преимущество, был создан способ передачи информации - язык, в котором каждая машинная команда представлена буквенно-цифровыми обозначениями, понятными человеку. Например add - сложить, mul - от слова multiply - умножить… В старых книгах можно встретить термин мнемокод (мнемонический, т. е. легко запоминающийся). Для перевода мнемокода в машинный язык была создана программа, которую назвали ассемблер (англ. assembler - сборщик), а язык мнемокодов соответственно стали называть языком ассемблера. Правильно говорить: я программирую на языке ассемблера, но в обиходе чаще говорят просто: программирую на ассемблере.
Программирование на ассемблере сохраняет все преимущества программирования в кодах, но избавляет от основного недостатка: исходный текст на языке ассемблера хотя и выглядит ужасно сложно, но все же может быть прочитан человеком (а если можно прочитать, то отчего бы не попробовать написать?). Тем не менее, ряд недостатков программирования в кодах при этой технологии сохраняется:
* Для работы на ассемблере необходимо достаточно глубокое знание особенностей компьютера, с которым имеем дело - попросту говоря, требуется высокая квалификация и серьезное отношение к работе;
* Программа пишется для конкретной машины и не может быть выполнена на компьютере, имеющем архитектурные отличия от того, для которого она предназначена;
* Программисту приходится каждое действие «разжевать и в рот компьютеру положить». Рассмотрим простой пример. Умножим вес некоторого товара на его цену, чтобы получить стоимость. На языке ассемблера некоторого абстрактного компьютера это может выглядеть примерно так:

mov  r0,VES
mul  r0,CENA
mov  STOIM,r0

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

STOIM=VES*CENA

Так появилась идея создать язык программирования высокого уровня.

Краткий исторический обзор языков высокого уровня

Подобные языки - Алгол и Фортран - появились уже в 50-е годы 20-го века, затем в этой отрасли имело место достаточно долгое и сложное развитие. На смену Алголу пришел ПЛ-1 (PL-1), затем появилось множество других языков. «Иных уж нет, а те далече»…
Появление PL-1 поначалу было принято программистами, мягко говоря, прохладно: они думали, что теперь их заставят все прежние программы переписывать на новый язык. Однако этот психологический барьер был преодолен, и новые языки стали создаваться один за другим, как новые сорта тюльпанов. Аналогия с тюльпанами особенно уместна потому, что большинство вновь создаваемых языков не представляло собой ничего принципиально нового.
Фортран (англ. FORmula TRANslation - перевод формул) превосходен как язык математиков и физиков, которые чаще всего сами не являются программистами, но ощущают потребность быстро создавать компактные и понятные коллегам-ученым программы для сложных числовых расчетов. Сейчас он имеет очень ограниченное применение: главным образом там, где от программы требуются учебно-демонстрационные свойства. Основной недостаток Фортрана - его непригодность для решения иных задач, кроме вычислений по формулам.
Кобол - Common Business-Oriented Language - в противоположность Фортрану, предназначен не для математиков и физиков, а для работников финансово-экономической сферы.
Алгол, Кобол и Фортран знаменуют собой древнекомпьютерную цивилизацию. Алгол умер, умерли его сыновья ПЛ-1 и Паскаль, но живо и процветает следующее поколение - языки семейства С. Переход от древности к средневековью в области языков начался чуть раньше, чем в области машин: в 1964 г. появился Бэйсик (англ. BASIC). Программисты моего поколения вспоминают его как кошмар, хотя, если вдуматься, в нем не было ничего такого ужасного. Возвращаюсь к мысли о том, что программа - техническое изделие, а техническим изделиям отнюдь не чужды понятия о красоте. У нас был старый верный Фортран: брутальный, как швейцарский электровоз, и крепкий, как ледокол «Красин». Его раздербанили на запчасти, 80% деталей слегка перекрасили, чтобы создать видимость обновления, еще 10% допилили напильником на коленке для обеспечения работы в интерпретационном режиме, и добавили множество подпорок, подставок и подкладок, обеспечивающих работу на абонентских терминалах. Какой же красоты можно было ожидать от такого изделия? На замену Бэйсику был придуман Фокал, но он тоже не обещал ни чудес, ни революций и в итоге оказался мертворожденным. Несмотря на критику, которой Бэйсик подвергался с «молодости», он пережил несколько реинкарнаций. Однако будущего у него нет.
В 1974 году был создан язык Паскаль, в котором воплотился многолетний опыт эксплуатации Алгола и его потомков, в частности ПЛ-1. Паскаль содержит все нужное, что было в его прототипах, и многое, чего в них не хватало, но не прощает «лишних вольностей». Его правила строги и прозрачны, что придает ему великолепные учебно-боевые свойства. Попросту говоря, изучение Паскаля по плечу даже школьнику, а освоив его однажды, вы сможете работать на нем всю жизнь, не испытывая нужды ни в чем другом. Почему он оказался заброшен и забыт?
Паскаль - не единственный потомок Алгола: даже если считать только мало-мальски жизнеспособные, то все равно получится довольно много. Остановлюсь на языке Ада. Он был создан в 1979-1980 годах в США и сразу стал стандартным языком в Пентагоне и НАСА. Предполагалось, что он станет общемировым, но этого не произошло: даже в гражданских ведомствах США он не нашел признания. В России этот язык практически неизвестен, хотя книжка (переводная с английского, разумеется) мне в руки попадалась.
Язык C (СИ) - не что иное как основательно испорченный Паскаль. Кто его испортил - задача из области уголовного следствия, я же сейчас остановлюсь на мотивах этого преступления. У тех людей, которые в 1970-е годы в гаражно-коленочных условиях создавали компьютер VAX и операционную систему UNIX, не было не только нормального экранного монитора, но даже и нормального принтера. Был телетайп - буквопечатающий телеграфный аппарат, который теперь можно увидеть разве что в музее. Он выводил данные на узкую бумажную ленту. Ленты вечно не хватало, и была она недешева, значит приходилось экономить. И вот люди решили придумать такой язык, чтобы каждое действие записывалось максимально коротко. В качестве прототипа выбрали Паскаль, что в общем-то вполне логично. Нос вытащили - хвост увяз: исходный текст стал трудно читаемым, а это не замедлило сказаться на трудоемкости разработки больших программ. Но тогда об этой стороне дела как-то не подумали, а потом, когда их разработка «пошла» и люди обзавелись мониторами, оказалось, что на новоизобретенном языке написано уже очень много, и возврат к настоящему Паскалю сочли нецелесообразным. Так началось триумфальное шествие языка C по планете и ее окрестностям. В последующие годы из языка C выросло целое семейство языков, как универсальных (C++, C#), так и специализированных (PHP, Java/JavaScript). Языки этого семейства в основном используются сейчас, хотя не представляют ничего революционного по сравнению с языками 70-х годов XX века, так что говорить о новой или новейшей эпохе применительно к языкам программирования не приходится. Это весьма печальная ситуация. Такого больше нет нигде в мире, ни в одной области человеческой деятельности.

Ассемблер vs языки высокого уровня

Рассмотрим общие черты всех упомянутых языков.
Запись типа STOIM=VES*CENA представляет собой абстракцию, не ориентированную на какой-либо конкретный компьютер. Программист на таком языке как бы «поднимается над полем боя», отсюда и название «язык высокого уровня». (Соответственно ассемблер - язык низкого уровня. У нас, как у шахтеров: чем ниже уровень, тем выше крутизна). Программисту не обязательно знать, «что там внутри», он даже может не задумываться, для какого компьютера он сейчас пишет. Стало быть, язык высокого уровня обладает кроссплатформенностью: написав на нем программу, мы потом сможем эксплуатировать ее на любом компьютере, не только нынешнем, но и будущем. Все, что для этого требуется, - это компилятор - программа, преобразующая исходный текст в машинный код. По сути, аналог ассемблера.
Казалось бы, языки высокого уровня во всем превосходят ассемблер?
Казалось бы, а оказалось, что все не так просто.
Во-первых, давайте опять-таки вспомним, что ЯВУ - это абстракция, а компьютер всегда конкретен. Мы написали исходный текст, а затем он должен быть переведен на машинный язык. Компилятор работает по известному ему алгоритму: читает исходный текст строка за строкой и преобразует каждую в некоторую последовательность машинных команд. Образующийся в результате этого машинный код всегда неоптимален: в нем много лишних действий. Например, вычисленное значение помещают в оперативную память, а для последующих вычислений читают его оттуда, хотя оно сохранилось в одном из регистров процессора. Часто компилятор «не владеет» тонкими возможностями компьютера, делает «в лоб» то, что хороший ассемблерщик сделал бы по-хитрому… Вот яркий пример. Вышеупомянутый алгоритм извлечения квадратного корня поразрядным взвешиванием на языке высокого уровня вообще невозможно записать (ну можно, конечно, постараться, но получится путано и коряво, и главное: все преимущества ассемблера с точки зрения быстродействия будут потеряны). Результат: программы, написанные на ЯВУ, неизбежно получаются более объемистыми и более «тормозными», чем аналогичные программы, написанные на ассемблере. Это объективный фактор. А есть и субъективный.
С вашего позволения, зайду несколько издалека. Как вы думаете, можно стать хорошим летчиком, не зная устройство самолета? Или, может быть, можно стать хорошим моряком, не зная навигации и лоции, гидрологии и метеорологии? А можно ли стать хорошим программистом, не зная устройства компьютера? Изучение языка C в том виде, как это делается в наших институтах, создает иллюзию, что можно… Что имеем в сухом остатке на сегодня? Язык, который каких-то 20-30 лет назад был языком мастеров и асов, стал языком халтурщиков и шабашников. Когда множество таких халтурщиков и шабашников оказывается на бирже труда, они друг другу только мешают. А руководитель фирмы-работонедателя (не важно, специализированная это программная фирма или просто завод) делает на этом свою игру. Вместо того чтобы искать серьезного ассемблерщика, а потом его холить и лелеять, он набирает дюжину недопрограммистов, любого из которых потом можно будет уволить без тяжких последствий для всего дела, и платит им соответствующую зарплату. Такие руководители не помнят дедушку Суворова с его наукой побеждать («воюй не числом, а умением») - они, наоборот, ставку делают на численное превосходство: «на биржу труда только свистни - сразу 30 человек в шляпах прибегут!» Но чтобы 30 человек в шляпах могли за какое-то вменяемое время создать приличный продукт, им нужен руководитель, который составит план работы, распределит работу между исполнителями и затем будет координировать их усилия. Найти такого руководителя среди C-программистов еще труднее, чем хорошего ассемблерщика! А без него будет много суетни, беготни, болтовни и возни, а дело никуда не продвинется. Если же вместо руководителя будет начальник, знающий в жизни две фразы: «идите работайте» и «ты уволен», то весь проект, скорее всего, закончится крахом.
Результат: сроки разработки программ затягиваются, затраты растут, страдает и качество продкута: программы очень хлопотны и затратны в эксплуатации, «тормозят», часто ошибаются, а то и просто не выполняют требуемые функции…
Часто можно услышать: «Программирование на ассемблере - это ужасно медленный процесс». Это миф. Надо признать, что некоторую историческую почву он под собой имеет, но все равно миф. Во времена Фортрана, действительно, программирование на Фортране обеспечивало скорость разработки в несколько раз больше, чем на ассемблере. Теоретически, при прочих равных условиях… Но с тех пор многое изменилось: и компьютеры, и задачи, и языки, и программисты. Да и сам ассемблер теперь другой.
Представим, что есть два программиста: один на ассемблере, другой на C, и мы посадили их в одной комнате друг напротив друга и дали задание разработать аналогичные программы (достаточно большие и сложные). Теоретически программист на сях имеет преимущество в скорости. Но это теоретически и при прочих равных условиях, а в реальной жизни прочие условия почти всегда неравны. Дело опять-таки в квалификации.
К чему я это все? - К тому, что хотя программирование на языках высокого уровня обещает некоторые преимущества перед программированием на ассемблере, но эти преимущества в действительности не так велики, как думает большинство в программном сообществе.

Что легче изучить: ассемблер или ЯВУ?

Часто можно услышать: ассемблеру очень легко научиться. С другой стороны, книга с названием вроде «Основы программирования на языке С» может иметь объем в тысячу страниц, и это только основы! На самом деле, как обычно в этом мире, не все так просто.
Язык ассемблера как таковой, действительно, изучить не представляет сложности. Но это не та вещь, которую можно изучить один раз и больше не изучать ничего. Как уже говорилось выше, программисту-ассемблерщику необходимо глубокое знание «кухни» той машины, для которой пишем. Машин разных на свете много, каждый год появляется что-нибудь новое, так что учиться приходится постоянно.

Ассемблер и С как инструменты

И вновь возвращаюсь к мысли, что программирование - не развлечение, а инженерная работа, а разные языки программирования в этой работе - инструменты.
Правило на все времена и для всех народов: каждый инструмент хорош для своей задачи. Есть алгоритмы, которые на ЯВУ реализуются с большим трудом, криво и путано - их имеет смысл писать на ассемблере. С другой стороны, есть целые большие пласты задач, которые никто никогда на ассемблере не пишет: это программы интернет-сайтов (там используются специализированные ЯВУ типа PHP и Java), различные программы в области экономики и бизнеса, основанные на реляционных базах данных (там тоже специализированные языки типа Клиппера, а также прикладные системы типа Microsoft Access, реализующие технологию «программирование без программирования»). Этот список можно продолжить.
Как выбрать подходящий язык для решения конкретной задачи? Единого рецепта нет. К тому же нельзя пренебрегать и субъективным фактором: каждый серьезный программист имеет свои предпочтения, далеко не всегда совпадающие с предпочтениями других людей. И еще. Выше мы обсудили трансляторы, объектные коды, построитель задач… Смысл этого всего в том, что большую сложную программу теперь не обязательно писать полностью на одном каком-то языке: ее можно разделить на части и для каждой части выбрать наиболее подходящий язык. Конечно, было бы совершенно неправильно, чтобы в программистском коллективе из 10 человек каждый работал бы на своем языке. Но писать основную программу на ЯВУ и делать в ней какие-то вставки на ассемблере с целью создать компактное и быстро работающее изделие - вполне хорошая практика.