Эволюция языков программирования

Глава 5. ЯЗЫКИ ПРОГРАМММИРОВАНИЯ

Эволюция языков программирования

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

Первым полноценным языком программирования стал язык Assembler - язык программирования низкого уровня, представляющий собой формат за­писи машинных команд, удобный для восприятия человеком (благодаря ему заметно уменьшилось время разработки и возросла надежность работы программ). Команды этого языка один в один соответствуют командам про­цессора и, фактически, представляют собой удобную символьную форму за­писи (мнемокод) команд и их аргументов. Кроме того, язык обеспечивает ба­зовые программные абстракции - связывание частей программы и данных через метки с символьными именами и директивы, которые позволяют вклю­чать в программу блоки данных (описанные явно или считанные из файла), повторить определённый фрагмент указанное число раз, компилировать фрагмент по условию, задавать адрес исполнения фрагмента, менять значе­ния меток в процессе компиляции, использовать макроопределения с пара­метрами и др. Язык ассемблера – машинозависимый. Это обусловлено тем, что каждая команда языка ассемблера обычно транслируется точно в одну команду машинного языка. Так как машинные языки у различных ЭВМ были разные, то и «ассемблеры» тоже различаются.

Следующий шаг - 1954 год, когда был разработан первый язык высокого уровня – Fortran (FORmula TRANslator – переводчик формул), широко ис­пользуемый при написании программ для сложных научных и инженерных вычислений. Языки высокого уровня имитируют естественные языки, ис­пользуя некоторые слова разговорного языка и общепринятые математиче­ские символы. Они более удобны для человека (с помощью них, можно пи­сать программы до нескольких тысяч строк длиной). Однако легко пони­маемый в коротких программах, этот язык становился трудно читаемым и управляемым, когда дело касалось больших программ. Эта задача была ре­шена посредством создания языков структурного программирования, таких как Algol (ALGOrithmic Language - алгоритмический язык) – 1958 г., Pascal (высокоуровневый язык программирования общего назначения) – 1970 г., C (стандартизированный процедурный язык программирования) – 1972 г.

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

В это же время создавались функциональные (аппликативные) языки, например, Lisp (LISt Processing – «обработка спи­сков») – 1958 г. и логиче­ские языки, например, Prolog (PROgramming in LOGic – программирование в терминах логики) - 1972 г. Хотя структурное программирование, при его ис­пользовании, дало выдающиеся результаты, но даже оно оказывалось труд­ным для исполнения тогда, когда программа дос­тигала определенной длинны. Для того чтобы написать более сложную (и длинную) программу, нужен был новый подход к программированию.

В итоге в конце 70-х – начале 80-х гг. прошлого столетия были разрабо­таны принципы объектно-ориентированного программирования, сочетающие лучшие принципы структурного программирования с новыми мощными кон­цепциями, базовые из которых называются инкапсуляцией, полиморфизмом и наследованием. Примером объектно-ориентированных языков являются: Object Pascal (объектно-ориентированный диалект языка Pascal), C++ (компилируемый строго типизированный язык программирования общего на­значения, основанный на синтаксисе C), Java (в достаточной степени схож с С++) и др. Объектно-ориентированные языки позволяют оптимально орга­низовать программы, разбивая проблему на составные части и работая с каж­дой из них по отдельности. Программа на объектно-ориентированном языке, решая некоторую задачу, по сути, описывает часть мира, относящуюся к этой задаче.

Большинство компьютерных архитектур и языков программирования ориентированы на последовательное выполнение операторов программы, но в настоящее время существуют программно-аппаратные комплексы, позво­ляющие организовать параллельное выполнение различных частей одного и того же вычислительного процесса. Для программирования таких систем не­обходима специальная поддержка со стороны средств программирования, в частности, языков программирования. Некоторые языки общего назначения содержат в себе элементы поддержки параллелизма, однако программирова­ние истинно параллельных систем требует специальных приемов. Для этой цели были созданы язык Occam (процедурный язык параллельного програм­мирования высокого уровня) – 1982 г., Linda (язык многопроцессорных сис­тем распределенной обработки данных) – 1985 г. и др.

Появление и активное развитие компьютерных сетей, широкое распро­странение высокопроизводительных компьютеров и ряд других факторов стали причиной создания многочисленных версий так называемых скрипто­вых языков. Эта языки первоначально ориентировались на использование в качестве внутренних управляющих языков во всякого рода сложных систе­мах. Однако многие из них вышли за пределы сферы своего изначального применения и используются ныне во многих областях. Характерными осо­бенностями данных языков являются, во-первых, их интерпретируемость (компиляция либо невозможна, либо нежелательна), во-вторых, простота синтаксиса и, в-третьих, легкая расширяемость. Они идеально подходят для использования в часто изменяемых небольших программах. К ним относятся – многоцелевой язык для решения системных задач Perl(1987), язык для описания сложного поведения веб-страниц JavaScript(1995) и др.

Со времени создания первых программируемых машин придумано ты­сячи языков программирования и каждый год их число пополняется новыми. Некоторыми языками умеют пользоваться лишь небольшое число их созда­телей, другие становятся известными миллионам людей. В развитии языков программирования специалисты отмечают пять поколений, которые посте­пенно улучшая свои характеристики, становятся всё более доступными в ос­воении пользователем. К языкам первого поколения относятся машинно-за­висимые языки. Второе поколение характеризуется созданием языков про­граммирования ассемблерного типа. Третье поколение начинается с появле­ния первого языка высокого уровня для решения инженерно-технических и научных задач. Языками четвертого поколения являются языки параллель­ного программирования, которые в отличие от всех ранее созданных средств ориентированы, прежде всего, на создание системного и прикладного про­граммного обеспечения для вычислительных сред нетрадиционной парал­лельной архитектуры. Естественные языки программирования, разрабаты­ваемые в настоящее время, составят пятое поколение и позволят непрофес­сиональному пользователю определять необходимые процедуры обработки информации, используя предложения языка, весьма близкого к естествен­ному и не требующего соблюдения особого синтаксиса.

5.2. Понятие «язык программирования»

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

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

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

Создатели языков по-разному толкуют понятие «язык программирова­ния». К наиболее распространенным утверждениям, признаваемым большин­ством разработчиков, относятся следующие. Язык программирования пред­назначен для написания компьютерных программ, применяемых для пере­дачи компьютеру инструкций по выполнению того или иного вычислитель­ного процесса и организации управления отдельными устройствам. Язык программирования – это способ передачи команд, приказов, четкого руково­дства к действию. Язык программирования использует специальные конст­рукции для определения и манипулирования структурами данных и управле­ния процессом вычислений. В отличие от естественных языков, язык про­граммирования имеет ограниченный запас слов (операторов) и строгие пра­вила их написания, а правила синтаксиса и семантики, как и для любого формального языка, однозначно и четко сформулированы.

Языки программирования, ориентированные на команды процессора и учитывающие его особенности, называют языками низкого уровня. Термино­логия «низкий уровень» означает близость операторов языка к машинному коду. Наиболее используемый и популярный язык низкого уровня - ассемб­лер. Каждый оператор этого языка представляет в виде мнемокодов команду микропроцессора (что позволяет запоминать команды не в виде последова­тельности двоичных нулей и единиц, а в виде осмысленных сокращений слов человеческого языка – обычно английских). Для каждого типа микропроцес­сора создаётся свой ассемблер, поддерживающий все его команды. При по­мощи языков низкого уровня создаются компактные и быстродействующие программы, так как программист получает доступ ко всем возможностям процессора (однако, при этом необходимо хорошо понимать устройство компьютера, а написанная программа не может быть использована на ком­пьютере с процессором другого типа). Такие языки программирования при­меняются для написания небольших системных приложений, драйверов уст­ройств, модулей стыков с нестандартным оборудованием, когда важны ком­пактность, быстродействие, прямой доступ к аппаратным ресурсам.

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

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

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

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

Разделение на компилируемые и интерпретируемые языки является не­сколько условным. Так, для любого традиционно компилируемого языка (на­пример, Pascal) можно написать интерпретатор, а для любого интерпрети­руемого языка можно создать компилятор (например, Lisp – изначально ин­терпретируемый, может компилироваться без каких бы то ни было огра­ничений). В реальных системах программирования (например, Java) смешаны технологии компиляции и интерпретации. В процессе отладки такие про­граммы можно выполнять по шагам (трассировать), а результирующий код не обязательно будет машинным. Он может быть, например, аппаратно-неза­висимым промежуточным кодом абстрактного процессора, который в даль­нейшем будет транслироваться в различных компьютерных архитектурах с помощью интерпретатора или компилятора в соответствующий машинный код. Этот подход позволяет использовать плюсы как интерпретаторов, так и компиляторов.

Итак, процесс создания программы в общем виде включает:

· составление исходного кода программы на языке программирования;

· этап трансляции, необходимый для создания объектного кода программы;

· построение загрузочного модуля, готового к исполнению.

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

· текстовый редактор;

· компилятор;

· редактор связей;

· отладчик;

· библиотеки функций;

· справочная система.