Рубрика:
Разработка /
Истоки программирования
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
АЛЕКСЕЙ ВТОРНИКОВ, независимый разработчик ПО, консультант, atues62@gmail.com
Философский камень программирования: язык С
Недавно минуло пять лет, как ушел из жизни один из наиболее влиятельных людей современности. Он не был бизнесменом. Он не был звездой шоу-бизнеса. И уж тем более он не был политиком. Он был программистом, и звали его Деннис Макалистер Ритчи (Dennis M. Ritchie)
Богатый урожай вырастает из горстки семян, а сдержанность рождает наслаждение.
Лиз Бреннан-Джобс
Трудно найти выдающегося ученого, изобретателя или новатора, чья биография уместилась бы в одном абзаце. О жизни Денниса Ритчи известно удивительно мало. Вернее будет сказать, что о ней неизвестно почти ничего.
Ритчи родился в сентябре 1941 года в Бронксвилле (штат Нью-Йорк) в семье исследователя из компании Bell Labs Алистера Ритчи, одного из авторов теории переключательных схем.
Деннис получил образование в Гарварде (физика и прикладная математика). В 1967 году стал сотрудником той же компании, где раньше трудился его отец. Ритчи вел очень замкнутый образ жизни и тщательно скрывал ее ото всех. Его коллега Роб Пайк как-то сказал о нем: «Более 20 лет я проработал на противоположной стороне зала и все же считаю, что не знал его хорошо».
12 октября 2011 года Пайк обнаружил тело Ритчи в его доме. Точная дата смерти осталась неизвестной: он скончался в период с 8 по 12 октября. Смерть Стива Джобса, случившаяся несколькими днями ранее, совершенно затмила собой уход Ритчи.
В отличие от личной профессиональная жизнь Ритчи сверкает ярче бриллианта. Ритчи в полной мере вкусил признание при жизни, что, правда, никак не отразилось на его привычной замкнутости. Вот только некоторые из полученных им наград и премий. Он – лауреат премии Тьюринга (1983) (совместно с Кеном Томпсоном) за разработку общей теории операционных систем и, в частности, за операционную систему UNIX. Он – лауреат медали Ричарда Хемминга (1990) (ивновь вместе с Кеном Томпсоном). В 1999-м он (как и прежде, с Кеном Томпсоном) получил из рук президента США Билла Клинтона Национальную медаль США за достижения в области технологий и инноваций, которые согласно официальной формулировке «закрепили лидерство Америки в информационном веке».
Профессиональная жизнь Д. Ритчи удивительно богата свершениями. Чтобы подробно рассказать о том, что он сделал для информатики и программирования, нужна большая книга. Может быть, когда-нибудь такая книга и появится. Мы, конечно, даже не будем пытаться объять необъятное, а расскажем лишь об одном факте его профессиональной биографии.
Откуда ноги растут?
Если спросить любого программиста, какой язык он считает самым важным, то он скорее всего назовет тот из них, с которым работает или которым владеет лучше всего. Счет таких языков пойдет на десятки. Но если поставить вопрос вдругой форме, а именно какой язык оказал наибольшее влияние на современное программирование, то круг претендентов резко сужается.
Хотя следующий перечень весьма субъективен, но, вероятно, 90% специалистов согласятся с тем, что список, состоящий из C++, Java и C#, является ответом на поставленный вопрос.
Сегодня модно оценивать популярность чего-либо по рейтингам. Языки программирования – не исключение. Один из наиболее известных и широко цитируемых – т.н. индекс TIOBE [1]. Если вы зайдете на указанный сайт, то убедитесь втом, что наш список очень похож на правду. Конечно, всякий рейтинг – не более чем приближение к истине, так что не будем придираться.
Но внимательно приглядевшись к рейтингу, вы, безусловно, обратите внимание на то, что в пятерку ведущих языков программирования стабильно входит еще один язык, которого нет в нашем перечне, а именно язык программирования C.
По большому счету наш первоначальный список никуда не годится – он должен состоять только из одного элемента, а именно C. Ведь именно C является предком всех языков из нашего shortlist. А места его потомков, как это ни покажется кому-то обидным, – во втором эшелоне.
Почему C?
А действительно, почему? Что в нем есть такого, что позволило C просуществовать больше 40 лет? И не просто просуществовать, но и продолжить активно использоваться. Мало того, что C является основой трех основных «монстров»; онпрародитель множества других языков программирования. Разумеется, наследники C отличаются от своего предка и внешне (т.е. синтаксически) и внутренне. Но, немного покопавшись, мы найдем явные следы C практически вбольшинстве из них.
Главная причина, по-видимому, в том, что C был создан программистом и для программистов. Автор языка не был связан маркетинговыми и рекламными обязательствами. Он делал инструмент для себя. Ему ничего и никому не надо было доказывать. Он был волен использовать то, что ему нравилось, изменять то, что не нравилось, добавлять то, что он считал необходимым, и выбрасывать то, что считал не нужным. Его не волновали «ленточки и колокольчики» (выражение Эдсгера В. Дейкстры). Он сделал ВЕЩЬ, не будучи обремененным никакими обязательствами, соглашениями и конъюнктурными соображениями.
Такое не часто случается, но если случается, то в результате получается шедевр «на все времена» (аналогичные истории произошли с языками Lisp и Forth). Автор языка не помышлял о вселенской славе, а просто сотворил гениальное детище, без которого сегодняшнее программирование было бы совсем другим. Конечно, «свято место пусто не бывает» – не было бы C, было бы что-то другое. Но история не оперирует сослагательным наклонением, в ней важны точный контекст и проверенные факты. Давайте к ним и обратимся.
Сначала контекст...
Все началось в 1961 году, когда Массачусетский технологический институт (MIT) приступил к разработке самой первой операционной системы разделения времени CTSS (Compatible Time-Sharing System). Что такое «разделение времени»? Молодым читателям, привыкшим к тому, что практически все современные операционные системы являются многозадачными, покажется, возможно, диким то, что так было не всегда. Первые 15-20 лет компьютерной эры доминировали операционные системы, построенные на совершенно иных принципах.
BCPL получился на редкость элегантным языком: исходный код программ, написанных на нем, удобно и даже приятно читать, что немаловажно для удобства работы |
Все существовавшие до 1961 года операционные системы были предназначены для пакетной обработки информации: программист писал программу, эта программа передавалась в машинный зал, оператор машинного зала готовил программу (обычно путем ее переноса на перфокарты) и ставил в очередь ожидающих задач (которые, собственно, и составляли собой пакет). Когда приходило время, программа отправлялась на обработку: подготовленная ранее колода перфокарт поступала в считывающее устройство. Информация загружалась и шла на обработку. Таким образом, между программистом и компьютером не было прямой связи: все, что программист мог, – это дождаться того момента, когда его программа будет поставлена на компьютер, и, скрестя пальцы, ждать, завершится ли все удачно или нет. Элементарная опечатка или брак перфокарты – и время вышло. На прогон элементарной задачи порой уходили часы и сутки. И всепотому, что время работы компьютера было дороже времени работы программиста.
Благодаря развитию технологической базы (появление сначала транзисторов, затем микросхем, а также новых устройств хранения информации) и конкуренции производителей ситуация начала меняться: компьютерное время иоборудование стали дешеветь, а стоимость работы программистов расти. Их количество увеличивалось, и все большему числу пользователей стал необходим прямой доступ к компьютеру. Наконец наступил момент, когда время работы программиста стало дороже времени работы компьютера и эффективность использования последних стала падать.
Заставлять пользователей ждать, когда машина освободится для решения их задач, стало экономически невыгодным. Нужно было найти способ нагрузить компьютер, а для этого прежде всего нужно было обеспечить одновременный доступ к компьютеру сразу нескольким пользователям. Так появилась концепция систем разделения времени: множество пользователей могли одновременно работать за одним компьютером. Каждому из них операционная система предоставляла небольшой промежуток (квант) времени, и у каждого из них складывалось впечатление, что вся вычислительная система принадлежит ему одному. Операционная система должна была следить за распределением ресурсов (процессора, памяти, устройств ввода/вывода), предотвращать конфликты и обеспечивать иллюзию единоличного владения компьютером. Кроме того, нужно было обеспечить интерактивность работы и избавиться наконец от миллионов перфокарт ибесконечных очередей в машинном зале.
CTSS, хотя и была экспериментальной разработкой (просуществовавшей тем не менее до 1972-го), показала, что за системами разделения времени будущее. В 1964 году MIT, компании General Electric (GE) и Bell Labs (BL) сформировали консорциум для разработки новой операционной системы разделения времени, получившей название MULTICS (MULTiplexed Information and Computing Service).
Поскольку опыта разработки и эксплуатации таких систем в середине 60-х годов было мало, то неудивительно, что для разработки MULTICS потребовались усилия трех крупных «игроков». Никто из участников поначалу не мог даже представить, какие проблемы их ожидают.
Задача оказалась сложной. Первопроходцы потратили на эту работу пять лет, и к 1969 году консорциум начал распадаться: из проекта вышли MIT и BL, а в 1970-м GE продала свой компьютерный бизнес. По счастью, история MULTICS наэтом не закончилась. Новый владелец (компания Honeywell) все-таки довел операционную систему до работоспособного состояния, и она работала в некоторых организациях (преимущественно в военных министерствах и университетах) до конца 2000 года. В 2007-м исходный код и документация MULTICS были переданы MIT и опубликованы на его сайте.
Несмотря на то что не все цели, поставленные разработчиками MULTICS, были достигнуты, она оказала решающее влияние на дальнейшее развитие операционных систем. Фактически MULTICS послужила предтечей семейства операционных систем UNIX.
В MULTICS был предложен и реализован ряд фундаментальных концепций: плоские файлы, иерархическая файловая система, разделяемое адресное пространство, виртуальная память, динамическое связывание библиотек.
В MULTICS были окончательно оформлены концепции процесса и мультизадачности. Были опробованы различные стратегии планирования, синхронизации и блокировки пользовательских и системных процессов.
MULTICS, хотя и ограниченно, допускала переконфигурирование системы «на ходу». Большое внимание уделялось вопросам безопасности и разграничения доступа.
Немаловажно также и то, что значительная часть MULTICS была написана на языке программирования высокого уровня (PL/1).
Общий размер MULTICS в откомпилированной и готовой к запуску форме составлял менее 140 килобайт, что по нынешним временам просто пустяк (хотя в те годы считалось, что система чересчур громоздка).
...а потом факты
Но вернемся к 1969 году. Со стороны BL в проекте участвовала группа под руководством Кена Томпсона (да-да, того самого Кена Томпсона, о котором мы упоминали ранее). Когда BL вышла из проекта, Томпсон решил, что будет глупо невоспользоваться знаниями, полученными в процессе работы над MULTICS. Одним из участников группы был Деннис Ритчи.
В распоряжении группы Томпсона оказался бесхозный компьютер PDP-7 (от Digital Equipment Corporation, DEC). Он был оснащен 8К памяти, состоящей из 18-битных слов (в те годы производители компьютеров активно экспериментировали с размером слова памяти). По признанию Д. Ритчи, сделанному в 1993 году, это было более чем спартанское оборудование даже по тем временам.
Томпсон решил реализовать некоторые из концепций MULTICS на PDP-7 и довольно быстро написал прототип операционной системы. Вот перечень того, что им было сделано: ядро операционной системы, редактор, ассемблер, простой интерпретатор команд, несколько основных утилит. Да-да, все это было реализовано на тех самых «несчастных» объемах памяти! Сегодня нам, привыкшим к гигабайтам и терабайтам, это кажется нереальным, но это так.
Язык программирования C – это пример исключительно удачной разработки |
Чуть позже, когда были реализованы загрузчик и линкер, появилась возможность связывать пользовательские программы с библиотеками, и операционная система Томпсона из исследовательского проекта стала превращаться в настоящий рабочий инструмент. Одним словом, Томпсон с сотрудниками создали на пустом месте практически полноценную среду программирования и исполнения программ. Это было тем, что впоследствии получило название UNIX.
Но Томсону пришлось очень нелегко – до реализации своего ассемблера он фактически программировал в машинных кодах. Да и после появления своего ассемблера легче не стало – ассемблер есть ассемблер и, как ни старайся, им иостанется. Это ему не очень нравилось (а кому бы, интересно, это могло понравиться), и Томпсон стал подыскивать подходящий язык высокого уровня, тем более что опыт MULTICS доказал – это возможно.
PL/1 был отвергнут сразу. Несмотря на мощную поддержку со стороны владельца языка (корпорации IBM), этот язык был слишком велик, слишком сложен и не слишком удачен. Томпсон попробовал FORTRAN, но быстро убедился в том, что этот выбор еще хуже. Нужно было что-то иное: более удобное, чем ассемблер, но не такое примитивное. Довольно скоро Томпсон нашел, как ему казалось, то, что нужно, – язык BCPL, созданный несколькими годами ранее Мартином Ричардсом (MIT). BCPL получился на редкость элегантным языком: исходный код программ, написанных на нем, удобно и даже приятно читать, что немаловажно для удобства работы. Кстати, популярный однострочный комментарий, начинающийся с последовательности «//», появился именно в BCPL.
Томпсон, по словам Д. Ритчи, «отфильтровал своими мозгами» BCPL (т.е. выбросил из него все, что только можно было выбросить) и буквально втиснул то, что осталось, в имевшиеся 8К. Язык получил имя B. Этимология названия языка осталась неясной: то ли Томпсон назвал его в честь своей жены, то ли в честь древней тибетской религии Бон (Bon), в которой практиковались магические ритуалы, заклинания и прочая эзотерика (что, кстати, больше похоже на правду).
Вот пример программы на языке B из руководства, составленного Кеном Томпсоном:
/* The following function will print a non-negative number,
n, to the base b, where 2<=b<=10. This routine uses
the fact that in the ANSCII character set, the digits O
to 9 have sequential code values. */
printn(n,b) {
extrn putchar;
auto a;
if(a=n/b) /* assignment, not test for equality */
printn(a, b); /* recursive */
putchar(n%b + '0');
}
Краткое пояснение для тех, кто плохо владеет английским языком. Эта функция решает важную задачу: представление неотрицательных целых чисел в системах счисления от 2 до 10. Обратите внимание, что решение задачи опирается натот факт, что в ASCII-кодировке символам от 0 до 9 соответствуют последовательные значения числовых кодов. Язык B допускал рекурсию. Благодаря этому функция получилась на удивление краткой и в то же время понятной.
Но язык B, несмотря на свою очевидную полезность, оставался языком экспериментальным и не слишком удобным. Его надо было, как говорят инженеры, «довести». Этой доводкой и занялся Деннис Ритчи.
Начало
Начало работ по переделке языка B относится к 1971 году. Почему не раньше? Причина состояла в том, что у Томпсона и Ритчи попросту не было подходящего компьютера. Они «выжали» из PDP-7 все, что только могли, и подошли кпределу его возможностей. Но в 1971-м у них появился новый компьютер PDP-11 (исключительно удачная разработка корпорации DEC), и с этого времени мир стал другим.
По словам Брайана Кернигана, «C – это инструмент, острый как бритва. С его помощью можно создать и шедевр, и кровавое месиво» |
Не следует думать, что все было придумано и реализовано сразу, легко и быстро. Язык C – это результат эволюции, постепенного приближения к идеалу. Два года понадобилось Ритчи для того, чтобы появился тот C, который мы привыкли видеть и использовать. Лишь в 1973 году, после многочисленных попыток, экспериментов, переделок и опробования различных вариантов, C как язык программирования явил себя миру в полной красе. Деннис Ритчи полностью спроектировал новый язык: это был действительно новый язык, а не «причесанный» вариант BCPL и B.
Явление C миру
Все три языка (BCPL, B, C) генеалогически относятся к той же группе языков программирования, что PL/1 и FORTRAN. К этой же группе, но только «приукрашенные» объектами, относятся также C++, Java и C#. Все это императивные языки программирования, ориентированные на вызовы функций и поддержку структур данных, которые могут быть непосредственно или без существенной потери эффективности отображены на память компьютера (переменные различных типов, массивы, строки и биты).
Почему Томпсон и Ритчи не ограничились языком B?
Во-первых, память в B рассматривалась как массив адресуемых слов памяти. С аппаратной точки зрения так оно и есть (и именно так память рассматривается при программировании на ассемблере), но с точки зрения программиста этобыло очень неудобно. Приходилось следить за тем, как интерпретировать содержимое ячейки памяти: это число, символ, адрес, строка или просто набор битов? Терялось главное преимущество языка программирования высокого уровня – абстрагирование от аппаратуры. Иными словами – в языке B отсутствовала типизация.
Во-вторых, в языке B была неразвитая адресная арифметика. Программисты на языке C прекрасно знают, что истинная мощь этого языка заключается в указателях (для тех, кто подзабыл или не знает, напомним: указатель – это просто адрес памяти). В сущности, указатель – это эквивалент косвенной адресации в ассемблерах. При косвенной адресации ячейка памяти содержит не сами данные, но адрес, по которому хранятся данные. Изменяя только лишь указатель, мы можем «одним движением руки» переходить от одних данных к другим.
Указатели – это суть языка программирования C. Но неумелое обращение с ними может породить и серьезные проблемы. Могут, например, появиться т.н. висячие ссылки, которые никуда не указывают. Неаккуратное обращение суказателем может привести к тому, что данные могут потеряться. Возможны утечки (leak) памяти.
Вторая опасность указателей в том, что они позволяют получить доступ к памяти, которая принадлежит другой программе или ядру операционной системы. По словам Брайана Кернигана, «C – это инструмент, острый как бритва. С егопомощью можно создать и шедевр, и кровавое месиво».
На самом деле в указателях нет ничего сложного – это просто способ доступа к данным, привнесенный из языка ассемблера и машинных кодов.
В-третьих, в языке B предусматривались либо отдельные переменные, либо области памяти, но не было ничего подобного структурам (записям), которые позволяли объединять переменные, указатели и массивы в композитный объект иманипулировать им как некой сущностью. Значение структур особенно ценно, если вспомнить, что именно они были положены в основу определения классов в объектно-ориентированном программировании.
В-четвертых, проблемы с компиляцией и связыванием отдельных модулей в единую исполняемую программу. Для решения этого Ритчи ввел препроцессор, который просматривал исходные коды программ и «собирал» нужную информацию перед тем, как программы будут компилироваться.
К концу 1973 года язык C приобрел знакомые нам сегодня черты. Главная цель была достигнута – появился полноценный язык программирования высокого уровня, пригодный для программирования операционных систем. И это было сделано – UNIX была переписана на C.
UNIX на C
Появление операционной системы, практически полностью написанной на языке программирования высокого уровня, произвело среди программистов эффект разорвавшейся бомбы. Лишь считанные проценты кода требовали программирования на языке ассемблера (например, обработка прерываний); все остальное было написано на C. Появилась реальная возможность не только ускорить процесс написания новых операционных систем, но и переноса существующих на новые аппаратные платформы. Так вскоре и случилось, и клоны UNIX стали множиться как грибы после дождя.
Появление операционной системы, практически полностью написанной на языке программирования высокого уровня, произвело среди программистов эффект разорвавшейся бомбы |
Разумеется, параллельно это привело к распространению языка программирования C. Из лабораторной разработки он становился промышленным стандартом. К началу 80-х годов прошлого века C стал первым среди равных, оттеснив практически все императивные языки на второй план.
Язык программирования C – это пример исключительно удачной разработки. Он не без недостатков (а где их нет?), но в целом это не умаляет его ценности. Он – действительно базовый элемент, на котором и по сей день держится всесистемное программное обеспечение. Операционные системы, базы данных, системы управления объектами (в том числе военными и космическими), обработка больших объемов данных, численные расчеты, реализация новых языков программирования... Список можно продолжать и продолжать.
Конечно, со временем появились и другие языки программирования. Но C по-прежнему остается единственным и неповторимым. И пока не видно, кто или что могло бы отодвинуть C. Попытки были, да толку?
K&R
По мере распространения UNIX программисты были вынуждены знакомиться с языком C по весьма кратким и порой неточным описаниям. Единственными авторитетными источниками знаний по языку служили исходные тексты программ UNIX и компилятор. Благодаря университетам (особенно университету в Беркли, штат Калифорния) операционная система стремительно совершенствовалась. Назрела необходимость дать программистам полноценное руководство.
В 1978 году Д. Ритчи в соавторстве с Б. Керниганом написали книгу «Язык программирования C». По первым буквам фамилий авторов на эту книгу обычно ссылаются как на K&R. Без сомнения, K&R стала одной из самых важных книг попрограммированию в истории информатики. Несмотря на солидный возраст, K&R остается, пожалуй, лучшим введением в язык программирования C, и каждый, кто программирует на этом языке, обязан ее прочесть. Эта небольшая книга служит своего рода эталоном при описании других языков программирования.
Деннис Ритчи очень взвешенно подходил к просьбам расширить язык. На них он обычно отвечал так: «Если тебе нужен PL/1, ты знаешь, где его взять». Это не консерватизм и не ревность создателя, а его осторожность и мудрость, которые позволили сохранить язык C небольшим, эффективным и популярным. Конечно, современный C несколько отличается от варианта, описанного в K&R, но если нужно изучить основы языка, понять его философию, проникнуться духом C, толучшего введения не найти.
- Индекс TIOBE – http://www.tiobe.com/tiobe-index.
- Б. Керниган и Д. Ритчи «Язык программирования C». Самая важная и главная книга по языку. Ищите второе издание, в котором отражено современное состояние языка.
- Домашняя страница Денниса Ритчи – https://www.bell-labs.com/usr/dmr/www. Оригинальные работы, доклады и руководства, написанные Д. Ритчи. Обратите внимание на статью «The Development of the C Language».
- Дж. Лайонс «Commentary on the Sixth Edition UNIX Operating System» – http://www.lemis.com/grog/Documentation/Lions/book.pdf и http://v6.cuzuco.com/v6.pdf. Возможно, вторая по значимости после K&R книга о языке. Это полная распечатка исходных кодов ядра UNIX и комментарии к ним. Книга сыграла выдающуюся роль в распространении UNIX и являлась настольной книгой хакеров (в изначальном понимании этого слова).
- Руководство М. Ричардса по BCPL – http://www.cl.cam.ac.uk/~mr10/bcplman.pdf. Несмотря на то что BCPL по современным меркам совсем уж «древний» язык программирования, его автор продолжает поддерживать язык по сей день.
- Андрей Богатырев «Руководство полного идиота по программированию (на языке C)». Доступный, интересный и качественный учебник. Его можно найти по адресу: http://www.lib.ru/CTOTOR/starterkit.txt.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|