Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
52
Добавлен:
20.04.2021
Размер:
124.49 Кб
Скачать

§. Классификация языков программирования.

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

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

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

Код на языке программирования высокого уровня, вообще говоря, не имеет командной привязки к аппаратным ресурсам, поэтому легко переносим на другие платформы, для которых поддерживается транслятор этого языка. Работа всех трансляторов строится по одному из двух принципов: интерпретация или компиляция.

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

В отличие от процедурного программирования объектно-ориентированный язык создает окружение в виде множества независимых объектов. К наиболее популярным объектноориентированным языкам относятся Си++, Delphi и Visual Basic.

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

При использовании декларативного языка программист указывает исходные информационные структуры, взаимосвязи между ними и то, какими свойствами должен обладать результат. Процедура получения результата в явном виде не формируется, понятие оператора отсутствует. Декларативные языки можно подразделить на два семейства – логические (типичный представитель – Пролог) и функциональные (Лисп).

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

Программа на логическом языке не описывает действий, а задает данные и соотношения между ними. После этого системе можно задавать вопросы. Машина перебирает известные и заданные в программе данные и находит ответ на вопрос. Порядок перебора не описывается в программе, а неявно задается самим языком. Классическим языком логического программирования считается ПРОЛОГ. Построение логической программы не требует алгоритмического мышления, программа описывает статические отношения объектов, а динамика находится в механизме перебора и скрыта от программиста.

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

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

Код на процедурном языке программирования последовательно меняет значения набора переменных, который называется состоянием. Если в начале работы программы имеет место начальное состояние X0 (значения входных данных), то в конце работы программы формируется конечное состояние Xn, а множество состояний образует конечную последовательность значений.

Состояние меняется с помощью команд присваивания новых значений, записываемых в виде y=F или y:=F, где y — переменная, а F — выражение. Команды выполняются одна за другой в определенном порядке; операторы, такие как if и while, позволяют изменить порядок выполнения этих команд в зависимости от текущего значения состояния. Такая концепция называется процедурной или императивной.

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

Нет состояний, поэтому нет изменяемых переменных в том смысле, в котором этот термин употребляется в императивном программировании.

Нет присваивания.

Нет циклов.

Последовательность команд не имеет значения, так как выражения независимы.

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

Вместо циклов используются рекурсивные функции.

§. Компиляция.

Процесс компиляции состоит из следующих этапов:

1. Лексический анализ (сканирование).

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

Используется таблица пограничных символов языка, которыми являются пробелы, разделители ( . , : ; ), знаки операций (+, -, :, *, :=) и ключевые слова языка. Создается таблица символических имен (идентификаторов), в которую сканер вносит каждое встретившееся ему имя, которое построено по правилам и не относится к ключевым словам. Для каждой константы запоминаются значение, тип, основание системы счисления, размер.

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

Код типа

Номер в таблице

лексемы

 

I

5

(I – признак того, что лексема – идентификатор; 5 – позиция в таблице идентификаторов) Каждой лексеме соответствует своя запись в таблице кодов лексем, причем первой записи соответствует первая лексема.

2. Синтаксический анализ (parsing).

На основании таблицы кодом лексем компилятор выделяет конструкции в соответствии с грамматикой языка. К конструкциям относятся операторы, блоки операторов, описания процедур и функций. Состав таких конструкций зависит от языка и реализации компилятора.

Все выделенные конструкции проходят полную синтаксическую проверку. В результате последовательность лексем преобразуется в синтаксическое дерево разбора (parse tree).

3. Семантический анализ.

Дерево разбора интерпретируется с точки зрения семантики (смысла) исходного языка, в том числе выполняются привязка идентификаторов к их декларациям, типам, проверка совместимости типов операндов, определение типов выражений и т. д. В том числе из дерева разбора строится дерево операций, соответствующее объектному (исполняемому) коду. В дереве операций по сравнению с деревом разбора исключены узлы, не имеющие

семантической (смысловой) нагрузки при генерации кода. К таким узлам относятся скобки, которые определяют порядок выполнения операций и теряют смысловую нагрузку после построения дерева разбора.

Пример синтаксически правильной конструкции, неправильной с точки зрения семантики

Объявленной константе (const) присваивается значение переменной.

Результатом анализа 1-3 является внутреннее представление программы, понятное компилятору, в одной из следующих форм:

oсвязочные списочные структуры, представляющие синтаксические деревья;

oмногоадресный код с явно именуемым результатом (тетрады);

oмногоадресный код с неявно именуемым результатом (триады);

oобратная (постфиксная) польская запись операций;

oассемблерный код или машинные команды.

Отличие одной формы от другой заключается в способе соединения операторов и операндов.

4.Распределение памяти.

5.Генерация кода.

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

6. Оптимизация кода.

Оптимизация программы – это исключение лишних операций, перестановка и изменение операций в компилируемой программе с целью повышения эффективности конечного объектного кода. В качестве составляющих критерия эффективности обычно берутся объем памяти, необходимый для выполнения программы, и скорость выполнения (быстродействие) программы.

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

Оптимизация может выполняться для следующих типовых синтаксических конструкций:

oпоследовательностей операций с одним входом и одним выходом: исключение лишних операций и перестановка операций;

oлогических выражений;

oциклов;

oвызовов процедур и функций: оптимизация передачи параметров.

Кроме того, выполняется машинно-зависимая оптимизация: распределение регистров процессора, оптимизация кода для процессоров, допускающих распараллеливание вычислений.

Соседние файлы в папке АОПИ. Лекции