В руководстве освещаются основы разработки приложений на языке Adobe® ActionScript® 3.0. Для наилучшего понимания описываемых принципов и техник вы должны быть знакомы с основными понятиями программирования, такими как типы данных, переменные, циклы и функции. Вы должны понимать основные принципы объектно-ориентированного программирования, такие как классы и наследование. Знание языка ActionScript версии 1.0 или 2.0 будет полезно, но необязательно.
Также в руководстве содержатся многочисленные примеры файлов, демонстрирующих принципы программирования с участием важных или часто используемых классов. Образцы файлов объединены в пакеты таким образом, чтобы их было легче загружать и использовать с Adobe® Flash® CS4 Professional. Они также могут включать файлы-оболочки. Тем не менее, ключевым образцом является сам код ActionScript 3.0, который можно использовать в любой среде разработки.
Эта книга поможет самостоятельно изучить язык С++, его принципы и концепции. Здесь изложены фундаментальные основы программирования, описаны принципы управления вводом-выводом, циклы, массивы, объектно-ориентированнеы подходы, а также создание полнофункционального приложения. Все главы содержат листинги программ, результаты их выполнения и анализ кода. Приведены ответы на часто задаваемы вопросы, а также упражнения и контрольные вопросы. Изложение книги не предполагает наличия у читателя предварительных знаний в области С++, а четкая организация материала позволит быстро и просто изучить язык.
ScriptC интерпретатор небольшого скриптового Си-подбного языка. ScriptC легко встраивается в приложения. Оптимизирован под инженерные и научные приложения, библиотека может быть использована как при разработке своих приложений так и приложениях позволяющие встраивание ActiveX компонентов ( Exel, Matlab и др).Язык имееет простой синтаксис, включающий в себя циклы, условные выражения, объявление функций, операторы форматированного вывода результатов расчетов, вывод информации об синтаксических ошибках. Язык обладает высокой скоростью исполнения интерпретированного кода, что позволяет его использовать в больших наукоемких расчетах не прибегая компиляторам традиционных языков программирования.
Прежде всего, микроконтроллер это процессор со всеми его "атрибутами", плюс встроенная, энергонезависимая память (программ и данных), что позволяет отказаться от внешней памяти программ и поместить программу в его энергонезависимую память.
Это позволяет создавать очень простые (в схемотехническом отношении) и компактные устройства, выполняющие, тем не менее, достаточно сложные функции. Иногда даже диву даешься: эта маленькая "штучка" заменяет целую "груду старого железа"
Любой микроконтроллер, по своим возможностям, конечно же, уступает процессору компьютера, но тем не менее, существует весьма обширный класс устройств, которые преимущественно реализуются именно на микроконтроллерах. И в самом деле, компьютер в карман не положишь и от батареек его не запитаешь. Поэтому, во многих случаях, микроконтроллерам просто нет альтернативы. "Сердцем" микроконтроллера является арифметико - логическое устройство (АЛУ).
Проще всего его представить в виде банального калькулятора, кнопками которого управляет программа, написанная на языке ассемблер (то есть, программист). Если вдуматься, то ничего особо сложного, в механизме управления такого рода калькулятором, нет. И в самом деле, если нужно, например, сложить числа А и В, то в тексте программы сначала задаются константы А и В, а затем дается команда "сложить". Программисту вовсе не обязательно знать, что происходит с нулями и единицами (разве только только для общего развития), ведь калькулятор он на то и калькулятор, чтобы избавить пользователя от "возни" с машинными кодами и прочими "неудобоваримостями".
Когда Вы работаете с компьютером, Вам и не нужно детально знать, что происходит в дебрях операционной системы.
Если Вы туда "полезете", то "с ума сойдете", а микроконтроллер, по своей сути, есть тот же самый компьютер, но только простой. Программисту только нужно детально знать, каким именно образом "приказать железяке" сделать то, что необходимо для достижения задуманного. Микроконтроллер можно представить себе как некий универсальный "набор" многофункциональных модулей (блоков), "рычаги управления" которыми находятся в руках программиста. Этих "рычагов" достаточно большое количество, и естественно, их нужно освоить и точно знать, что именно произойдет, если "дернуть" (дать команду на языке ассемблер) за тот или иной "рычаг". Вот здесь-то уже нужно знать, как "отче наше", каждую деталь и не жалеть на это "узнавание" времени. Только таким образом пустую "болванку" (незапрограммированый ПИК) можно "заставить"
выполнять какие-то "осмысленные" действия, результат большей части которых можно проверить в симуляторе MPLAB (об этом - позднее), даже не записывая программу в ПИК.
Итак, необходим переход к "модульному" мышлению. Любой микроконтроллер можно уподобить детскому конструктору, в состав которого входит множество всяких предметов, манипулируя с которыми, можно получить тот или иной конечный "продукт". Давайте с ними разберемся и "разложим все по полочкам". В качестве примера я буду использовать один из самых распространенных PIC контроллеров PIC16F84A. Он является как бы "проматерью" более сложных ПИКов, содержит минимальный "набор" модулей и как нельзя лучше подходит для первичного "въезда в м/контроллеры".
Энергонезависимая память.
Начнем с энергонезависимой памяти (память программ и память данных).
Информация, заложенная в энергонезависимую память, сохраняется при выключении питания, и поэтому именно в нее записывается программа.
То "место" энергонезависимой памяти, куда записывается программа, называется памятью программ. Объем памяти программ может быть различен. Для PIC16F84A, он составляет 1024 слова. Это означает, что он предназначен для работы с программами, объем которых не превышает 111024 слов.
Слово памяти программ не равно одному байту (8 бит), а больше его (14 бит). Отдельная команда, которую ПИК будет в дальнейшем выполнять, занимает одно слово в памяти программ. В зависимости от названия этой команды в ассемблере, слово принимает то или иное числовое значение в машинном коде. После записи в ПИК "прошивки" программы, слова памяти программ (машинные коды) как бы "превращаются" в команды, которые располагаются, в памяти программ, в том же порядке, в котором они следуют в исходном тексте программы, написанном на языке ассемблер, и в том же порядке им присваиваются адреса, при обращении к которым, та или иная команда "извлекается" из памяти программ для ее выполнения. Последовательность же их выполнения определяется логикой программы. Это означает то, что выполнение команд может происходить не в порядке последовательного возрастания их адресов, с шагом в одну позицию (так называемый инкремент), а "скачком". Дело в том, что только уж самые простейшие программы, в пределах одного их полного цикла, обходятся без этих "скачков", называемых переходами, и выполняются строго последовательно. В остальных же случаях, так называемая (мной) "рабочая точка программы" "мечется по тексту программы как угорелая" (как раз благодаря этим самым переходам).
Термин "рабочая точка программы" - моя "самодеятельность". В свое время, я был очень сильно удивлен отсутствием чего-то подобного в информации, связанной с объяснением работы программ. Казалось бы, чего проще, по аналогии, например, с рабочей точкой транзистора, сделать более комфортным "въезд в механику" работы программ? Так нет же, как будто специально, придумываются такие "головокружительные заменители", причем, в различных случаях, разные, что запутаться в этом очень просто. Итак, рабочую точку программы можно представить себе в виде некоего "шарика от пинг-понга", который "скачет" по командам текста программы в соответствии с алгоритмом (логикой) исполнения программы. На какую команду "шарик скакнул", та команда и исполняется. После этого он "перескакивает" на другую команду, она исполняется, и т.д. Эти "скачки" происходят непрерывно и в течение всего времени включения питания устройства (исполнения программы).
Любая более-менее сложная программа разбивается на части, которые выполняют отдельные функции (своего рода программки в программе) и которые называются подпрограммами. Атрибут любой подпрограммы - функциональная законченность производимых в ней действий.
По сути своей, эта "выдумка" введена в программирование для удобства реализации принципа "разделяй и властвуй": "врага" ведь гораздо легче "разгромить по частям, чем в общей массе". Да и порядка больше.
Безусловные переходы (переходы без условия) между подпрограммами (если они последовательно не переходят одна в другую), осуществляются при помощи команд безусловных переходов, в которых обязательно указывается адрес команды в памяти программ (косвенно - в виде названия подпрограммы или метки), на которую нужно перейти. Существуют также переходы с условием (условные переходы), то есть, с задействованием так называемого стека. Более подробно о переходах я расскажу позднее. Адреса команд определяются счетчиком команд (он называется PC). То есть, каждому состоянию счетчика команд соответствует одна из команд программы. Если команда простая, то счетчик просто инкрементируется (последовательно выполняется следующая команда), а если команда сложная (например, команда перехода или возврата), то счетчик команд изменяет свое состояние "скачком", активируя соответствующую команду.
Примечание: инкремент - увеличение на единицу величины числа, с которым производится эта операция, а декремент - уменьшение на единицу (так называемые комплиментарные операции). В простейшем случае, то есть в случае отсутствия в программе переходов, счетчик команд PC, начиная с команды "старта" (нулевой адрес), многократно инкрементируется, 12 последовательно активизируя все команды в памяти программ. Это означает, что в большинстве случаев, за каждый так называемый машинный цикл (такт работы программы: для ПИКов он равен четырем периодам тактового генератора) работы ПИКа, происходит исполнение одной команды. Есть и команды исполнение которых происходит за 2 машинных цикла (м.ц.), но их меньше. Команд, которые исполняются за 3 м.ц. и более нет. Таким вот образом, на большинстве участков программы (я их называю "линейными участками"), последовательно и перебираются адреса в памяти программ (команды последовательно исполняются).
В более сложных программах, с большим количеством условных и безусловных переходов, работу счетчика команд PC можно охарактеризовать фразой "Фигаро здесь, Фигаро там". 1 машинный цикл (м.ц.) равен 4-м периодам тактового генератора ПИКа. Следовательно, при использовании кварца на 4 Мгц., 1 м.ц.=1 мкс. Выполнение программы, в рабочем режиме (кроме работы в режиме пониженного энергопотребления SLEEP), никогда не останавливается, то есть, за каждый машинный цикл (или за 2, если команда исполняется за 2 м.ц.) должно выполняться какое-либо действие (команда). Тактовый генератор, формирующий машинные циклы, работает постоянно. Если его работу прервать, то исполнение программы прекратится.
Может сложиться ложное представление о том, что работу программы можно на какое-то время остановить, используя одну или несколько команд – "пустышек", не производящих полезных действий (есть такая команда NOP). Это представление не верно, так как в этом случае, речь идет только о задержке выполнения следующих команд, а не об остановке исполнения программы. Программа исполняется и в этом случае, так как "пустышка" есть та же самая команда программы, только не производящая никаких действий (короткая задержка). Если же нужно задержать выполнение каких-либо последующих команд на относительно длительное время, то применяются специальные, циклические подпрограммы задержек, о которых я расскажу позднее. Даже тогда, когда программа "зависает" ("глюк"), она исполняется, просто только не так, как нужно. Остановить (в буквальном смысле этого слова) исполнение программы можно только прекратив работу тактового генератора. Это происходит при переходе в режим пониженного энергопотребления (SLEEP), который используется в работе достаточно специфических устройств. Например, пультов дистанционного управления (и т.д.).
Отсюда следует вывод: программы, не использующие режим SLEEP (а таких - большинство), для обеспечения непрерывного выполнения команд программы, обязательно должны быть циклическими, то есть, иметь так называемый полный цикл программы, причем, многократно повторяющийся в течение всего времени включения питания. Проще говоря, рабочая точка программы должна непрерывно (не останавливаясь) "мотать кольца" полного цикла программы (непрерывно переходить с одного "кольца" на другое).
Общие выводы:
1. Команды программы "лежат" в памяти программ в порядке расположения команд в тексте программы.
2. Адреса этих команд находятся в счетчике команд PC и каждому адресу соответствует одна из команд программы.
3. Команда активируется (исполняется), если в счетчике команд находится ее адрес.
4. Активация команд происходит либо последовательно (на "линейном" участке программы), либо с переходом ("скачком") на другую команду (при выполнении команд переходов), с которой может начинаться как подпрограмма (переход на исполнение подпрограммы), так и группа команд, выделенная меткой (переход на исполнение группы команд, которой не присвоен "статус" подпрограммы).
5. Выполнение команд программы никогда не останавливается (за исключением режима SLEEP), и поэтому программа должна быть циклической.
Кроме памяти программ, PIC16F84A имеет энергонезависимую память данных (EEPROM память данных). Она предназначена для сохранения данных, имеющих место быть на момент выключения питания устройства, в целях их использования в дальнейшем (после следующего включения питания). Так же, как и память программ, память данных состоит из ячеек, в которых "лежат" слова. Слово памяти данных равно одному байту (8 бит). В PIC16F84A, объем памяти данных составляет 64 байта. Байты, хранящиеся в памяти данных, предназначены для их считывания в стандартные 8-битные регистры, речь о которых пойдет далее. Данные из этих регистров могут быть записаны в EEPROM память данных, то есть, может быть организован обмен данными между памятью данных и регистрами. Например, именно EEPROM память данных я использовал в своем частотомере для сохранения последних, перед выключением питания, настроек. Она же используется и для установки значений промежуточной частоты. Во многих программах, память данных вообще не используется, но это "вещь" исключительно полезная, и далее я расскажу о ней подробнее.
В данной статье рассмотрены принципы, помогающие компилятору Delphi генерировать более оптимальный с точки зрения скорости код. Если Вы не хотите вникать в подробности, в конце статьи есть «свод правил», которые рекомендуется соблюдать при написании программ.
Компилятор Delphi относится к разряду оптимизирующих. Но насколько качественно проводится оптимизация? Как «помочь» компилятору создать более быстрый код? Давайте разберемся с этим на экспериментах.
Оптимизация константных выражений
Пример 1:
С точки зрения оптимизации код можно упростить еще на этапе компиляции до
Но написанный выше листинг преобразуется в
С одной стороны компилятор не «сообразил», что значение переменной «a» можно преобразовать в константу и сложить с другой константой (которая, заметим, подставлена именно как константа) на этапе компиляции, с другой стороны был применен весьма хитрый трюк с LEA (об этом ниже). Тем не менее, код
в любом случае быстрее и короче.
Пример 2:
Скомпилированный код будет выглядеть
А ведь значение, присвоенной переменной «а» являлось константой и наш пример можно было бы переписать как:
Пример 3:
После компиляции получаем:
Т.е. компилятор преобразовал код так, как он был написан, а ведь можно было бы просто записать:
Оптимизация алгебраических выражений
Пример 4:
После компиляции эти переменные будут удалены, причем с предупреждением
Пример 5:
Код скомпилируется как есть! Таким образом мы обманули компилятор псевдо использованием переменных. Delphi не исправляет нашей «кривости», поэтому эта задача ложится исключительно на плечи программиста.
Пример 6:
Данный код можно оптимизировать до
И этого Delphi за нас не сделает.
Пример 7:
В данном примере первую строчку можно безболезненно удалить, что Delphi делать умеет.
Пример 8:
В данном случае можно избавится от одной операции умножения, присвоив значение выражения a*b временной переменной. Анализ ассемблерного листинга показывает, что компилятор именно так и поступает. Тем не менее, поменяв второе подвыражение на ((b*a)>0), компилятор принимает выражения за разные и генерирует умножение для обоих случаев, не смотря на то, что результат одинаков.
Оптимизация арифметических операций
Сложение и вычитание
Применение инструкции LEA вместо ADD позволяет производить сумму 3х операндов (двух переменных и одной константы) за один такт. Трюк заключается в том представление ближних указателей эквивалентно их фактическому значению, поэтому результат, возвращенный LEA равен сумме ее операндов. При возможности Delphi производит такую замену.
Деление
Операция деления требует гораздо больше тактов процессора, нежели умножение, поэтому замена деления на умножение может значительно ускорить работу. Существуют формулы, позволяющие выполнять такое преобразование. Тем не менее, Delphi не использует такую оптимизацию. Деление на степень двойки можно заменять сдвигом вправо на n бит, но даже в этом случае получаем следующий код:
Здесь учитывается особенность самой операции div – округление в большую сторону. Поэтому, если можно пренебрегать округлением, используйте c:=a shr 1 вместо с:=a div 2.
Умножение
Умножение на степень двойки можно заменять сдвигами битов. Delphi заменяет умножение сдвигами при умножении на 4,8,16 итд. При умножении на 2 производится суммированием переменной с собой.
Умножать на 3,5,6,7,8,10 и т. д. можно и без операции умножения – расписав выражение по формуле (a shl n)+a, где n – показатель степени двойки. Например, при умножении на 3 n=1. Delphi при возможности прибегает к этому трюку. Заметим, операнд LEA умеет умножать регистр на 2,4,8, что также при возможности используется компилятором. Например, умножение на 3 преобразуется в инструкцию
Оптимизация case of
Анализ скомпилированного кода показывает, что Delphi проводит утрамбовку дерева. Т.е. значения case сортируются и выбор нужного элемента производится при помощи двоичного поиска.
В случае, если элементы case of выстраиваются в арифметической прогрессии, компилятор формирует таблицу переходов. Т.е. создается массив указателей с индексами элементов, поэтому выбор нужно элемента выполняется за одну итерацию независимо от количества элементов.
Оптимизация циклов
Разворачивание циклов – не производится. Разворачивание циклов весьма спорный момент в оптимизации, поэтому принять грамотное решение может только человек. Delphi не производит разворачивания ни больших, ни маленьких циклов.
Слияние циклов – не производится. Если два цикла, следующие друг за другом имеют одинаковые границы итерационной переменной, разумно оба цикла объединить в один.
Вынесение инвариантного кода за пределы цикла – не выносится. Наиболее распространенный недочет – условие цикла записывается как:
Delphi будет при каждой итерации вызывать метод count, вычитать из результата 1 и потом уже сверять. Настоятельно рекомендуется переписывать подобный код как
Весь код VCL написан с нарушением этого правила. Очевидно, что проще подобного рода оптимизацию встроить в компилятор, нежели переписывать VCL :)
Замена циклов с предусловием на циклы с постусловием – производится. Циклы с постусловием имеют главное преимущество над другими видами циклов (с предусловием и с условием в середине) – они содержат всего одно ветвление. Delphi производит такую замену.
Замена инкремента на декремент – не производится. Более того, даже декрементный цикл компилируется в неоптимальный код, т.к. не используется флаг ZF. Вместо этого происходит сравнивание значения регистра с 0.
Удаление ветвлений – не производится.
Вывод:
1. Не используйте переменные для временного хранения констант или обязательно объявляйте «магические» числа как const, либо подставляйте в код непосредственные значения
2. Неиспользуемыми объявлениями и присвоениями можно безболезненно пренебрегать – Delphi умеет их вычищать.
3. Внимательно следите за использованием переменных, в частности лишним присвоениям их значений друг другу. Такого рода оптимизации Delphi делать не умеет.
4. Используйте свернутые математические выражения. (например, (3*a - a) /2 упрощается до a). Delphi не умеет упрощать математические выражения. (Да и что говорить, даже MathCAD не всегда грамотно умеет делать такие преобразования).
5. Не используйте конструкции типа a:=10*sin(45*pi/180); Delphi не вычислит эту константу на этапе компиляции, напротив, будет послушно вызывать sin и pi по ходу выполнения программы! В случае, если угол является переменной, по крайней мере pi можно заменить константой 3,1415...
6. Delphi прекрасно справляется с выражениями, полностью составленных из констант – они вычисляются на этапе компиляции.
7. Внимательно следите за условиями и их границами. Компилятор Delphi не умеет обнаруживать заведомо ложных условий. Также он не умеет удалять заведомо лишние условия. Например, (a>0) and (a<15616) and (a<>0)
8. Если в условии несколько раз проверяется одно и тоже выражение, следите, чтобы оно было выражено во всех конструкциях одинаково. В противном случае скомпилированный код будет не оптимален. Например, if ((a*b)>0) and ((a*b)<1024) then... При перестановке во втором случае b*a смысл выражения не изменится, но код будет иметь уже на одну операцию умножения, а две. Можно временно присвоить проверяемое выражение временной переменной, а затем уже проверять полученное значение.
9. Сообщение «Combining signed and unsigned types – widened both operands» сообщает не только о потенциальной ошибке – также вследствие преобразования мы теряем производительность. Например, z – объявлена как ineteger. условие if z>$abcd6123 then z:= $abcd6123; несмотря на его правильность вызовет данное предупреждение. Сгенерированный код будет, выполнять преобразования величин до 64-х бит, и дальнейшее уже сравнение 64-х битных операндов. Если изменить тип z на cardinal, мы избавимся от предупреждения и получим 3 строки кода, вместо 8 !
10. Delphi умеет оптимизировать сложение, умножение и частично деление. При делении на степень двойки, если не важно округление до большего, рекомендуется пользоваться shr 1 вместо div 2.
11. В case of при возможности используйте элементы, расположенные в арифметической прогрессии. Тем не менее, даже при невыполнении данного условия мы получим качественный код после утрамбовки дерева.
12. Выносите инвариантный код за тело цикла. Наиболее частая ошибка – for i:=1 to length(str) do... Дело в том, что при каждой итерации будет вызываться функция length, что пагубно скажется на производительности. Рекомендуется длину строки заранее присвоить переменной. Также не включайте в тело цикла код, заведомо не зависящий от изменения итерационной переменной.
Сравнивая Delphi с компиляторами Visual C++, WATCOM, Borland C++ (тестирование данных компиляторов приведено в [1]) приходим к выводу, что Delphi по своим оптимизирующим свойствам аналогичен Borland C++ (а кто сомневался? ;) ). Учитывая, что Borland C++ по итогам сравнения оказался последним, делаем несложный вывод. Весьма печален и тот факт, что большинство кода VCL написано с точки зрения «красоты» кода, а не его оптимальности с точки зрения скорости. Например, не соблюдается правило 12.
Так уж сложилось у веб-мастеров, что на их сайты заходят как пользователи, так и спамерские роботы. Если первые заходят на сайт для поиска нужной информации, то вторым непременно подавай адреса e-mail'ов. Так называемые флудеры, которым попросту от нечего делать или от небольшого интеллекта так и хочется завалить ваши новости, статьи бестолковой лексикой. Ну а форум сообщениями, явно не относящимися к тематике рассматриваемой темы. Это неизбежно, как и то что за ночью приходит день и наоборот. Но с этим нужно бороться - иначе ваши порядочные пользователи вовсе потеряют интерес к вашему сайту - можете не сомневаться.
Самым простой метод противодействия данным явлениям - это банить таких посетителей по ip. Проще говоря, закрывать им доступ на ваш сайт. Данный метод пусть и не идеальная защита, но порядка 90% защиту он вам обеспечит. а это уже лучше, чем ничего.
Далее рассмотрим, как данная техника защиты работает на практике и как ее можно реализовать на php.
За дело
Для начала нам нужно получить список ip-адресов, которым будет закрыт доступ на наш сайт. Еще потребуется использовать массивы и циклы - вот где нам и пригодится php.
Дальше - больше :)
Для получения ip-адреса посетителя можно использовать суперглобальную переменную $_SERVER['REMOTE_ADDR'].
На php это реализуется следующим образом:
.
.
А теперь разберемся с остальными переменными нашего мини-скрипта. Создаем массив ($ban), куда помещаем все забаненые ip:
.
.
Как видите, ip нужно помещать в одинарные кавычки и отделять их друг от друга запятой (все просто и ясно - за что я и люблю php)
В следующая переменная ($count) будет занесено количество этих самых ip:
.
.
Для чего это все нужно - читайте дальше.
Что мы имеем:
1) IP адрес определенного посетителя - переменная $ip;
2) массив запрещенных IP-адресов - $ban;
3) число элементов этого массива - $count.
Все элементарно просто - берем и сверяем адрес $ip со списком запрещенных - а так как у нас их может быть несколько, то для этого понадобится обход всего массива забаненых адресов.
Эту работу за нас легко проделает цикл for:
.
.
.
Вот и получается, что если проверяемый адрес совпадет с одним из наших "нехороших", то мы и дадим знать его обладателю - I'm sorry, you've been banned. 68.225.34.86.
А вот и весь скрипт целиком - это для тех, кому лень его собирать по частям из статьи: