Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 19
Текст из файла (страница 19)
В основных чертах данная стратегия сработала.Но почти все детали оказались не такими, как я рассчитывал. Чтобы ответить на второй вопрос, я просто огляделся по сторонам — на каких системах реально использовался С тт!гЬ С!аззез? Практически на любых: от таких маленьких, что иа них и компилятор-то не мог работать, до мейнфреймов и суперкомпьютеров. Среди ОС были и такие, о которых я даже не слыхивал.
Отсюда следовали выводы, что высокая степень переносимости и возможность выполнять кросс-компиляцию абсолютно необходимы и что я не могу делать никаких предположений о размере и быстродействии машин, на которых будет работать сгенерированный код. Однако для построения компилятора потребовались какие-то допущения о системе, используемой для разработки программ. Я предположил, что скорость действия будет не монсе 1 М1РБ (миллион команд в секунду), а память — не менее 1 Мб.
Предположение достаточно рискованное, поскольку в то время многие потенциальные пользователи работали на разделяемой Р1)Р11 или другой системе, це слишком мощной или эксплуатируемой в режиме разделения времени. Я не предвидел революции, вызванной пришествием персональных компьютеров, но получилось так, что С1гопг, превзошедший намеченные показатели производительности, мог работать (правда, на пределе) даже на 1ВМ РС/АТ. Это ШИИИИИВ Рождение С++ доказало, что С++ может быть эффективным языком на ПК, и разработчикам коммерческого программного обеспечения есть чем заняться. Размышляя над четвертым вопросом, я пришел к следующим выводам.
В языке не должно быть средств, требующих особо изощренного компилятора или поддержки во время исполнения, надо использовать существующие компоновщики, а сгенерированный код должен быть эффективен (по сравнению с С), начиная с самых первых версий. 3.3. Компилятор Огоп1 Компилятор СЕгопг для языка С84 был спроектирован и реализован в период между весной 1982 г. и летом 1983 г.
Первый пользователь за пределами Центра исследований по вычислительной технике, Джим Коплиен ()1ш Сор!!еп), получил копию в июле 1983 г. Джим работал в группе, занимавшейся экспериментальными исследованиями в области коммутации сетей, в отделении Ве!! ЕаЬз в Напервилле, штат Иллино1йс. До этого Коплнен некоторое время пользовался С ъчгЬ С!аззсз.
За указанный период я спроектировал С84, написал черновик справочного руководства (опубликованного 1 января 1984 г. [8ггопзггцр, 1984!), спроектировал библиотеку сопр1ех для работы с комплексными числами, которую мы реализовали вместе с Леони Роуз (1.еоп(е Козе) !Козе, 1984 !, спроектировал и реализовал вместе с Джонатаном Шопиро первый класс зсг(пд для работы со строками, занимался сопровождением и переносом на другие платформы С вчгЬ С!аззез, поддерживал пользователей С вчгЬ С!аззез и готовил их к переходу на С84.
В общем, беспокойные выдались полтора года. СЕгопгбыл (и остается) традиционным компилятором неполного цикла (Егопсепс! сотар!!ег). Он проверяет синтаксис и семантику языка, строит внутреннее представление программы, анализирует и переупорядочивает его и на выходе генерирует файл, подаваемый какому-нибудь генератору кода. Внутреннее представление— это граф, в котором есть по одной таблице символов на каждую область действия.
Общая стратегия компиляции такова: читать по одному глобальному объявлению из исходного файла и формировать вывод только тогда, когда объявление полностью проанализировано. На практике это означает, что компилятору нужно достаточно памяти, чтобы хранить представление всех глобальных имен, типов, а также полный граф одной функции. Несколькими годами позже я выполнил ряд измерений работы СЕгопг и обнаружил, что потребление памяти стабилизируется на уровне 600 Кб на Е)ЕС ЧАХ практически независимо от компилируемой программы.
Поэтому в 1986 г. мне удалось выполнить перенос СЕгопг на РС/АТ. К моменту выхода версии 1.0 в 1985 г. СЕгопг представлял собой примерно 12 тыс. строк кода на С++. Организация СЕгопг довольно обычна, разве что в нем используется много таблиц символов вместо одной. Первоначально СЕгопс был написан на С ггйЬ С!аззез (а на чем же еще?), но скоро был переписан на С84, так что самый первый работающий компилятор С++ написан на С++. Даже в первой версии СЕгопг интенсивно исполъзовались классы и наследование. Однако виртуальных функций в нем не было. МИИИИИИИ Компилятор Огоп1 С!гонг — это компилятор неполного цикла. В реальных проектах его одного было недостаточно.
Ему необходим драйвер, который пропускает исходный файл через препроцессор С (Срр), затем подает выход Срр на вход С!гонг, а выход С1гопг — на вход компилятора С (см. рис. 3.1). Кроме того, драйвер должен был обеспечить динамическую (во время исполнения) иннпиализацию. В С!гоп! 3.0 драйвер стал еще более сложным, поскольку было реализовано автоматическое инстанцированне (см.
раздел 15.2) шаблонов !МсС! пйеу, 19921. Рис. 3.1 о время, требующееся С!гонг для записи промежуточного С-кода; о время, нужное компилятору С, чтобы прочитать промежуточный С-код; З.З.1. Генерирование С-кода Самый необычный для своего времени аспект С!гонг состоял в том, что он генерировал С-код. Это вызвало много путаницы. С1гопг генерировал С-код, поскольку первая реализация должна была быть хорошо переносимой, а я считал, что более переносимого ассемблера, чем С, не найти. Я легко мог бы сгенерировать внутренний формат для кодогенератора или вывести файл на языке ассемблера, но это было не нужно моим пользователям.
С помощью ассемблера и внутреннего кодогенератора невозможно было бы обслужить более четверти всех пользователей, и ни при каких обстоятельствах я не смог бы написать, скажем, шесть кодогенераторов, чтобы покрыть запросы 90% пользователей. Поэтому я решил, что использование С в качестве внутреннего формата — это разумный выбор. Позже построение компиляторов, генерирующих С-код, стало распространенной практикой. Так были реализованы языки Аг1а, Е!!!е1, Мог)н!а-3, 1ззр и Вша!11аПг. Я получил высокую переносимость ценой небольшого увеличения времени компиляции.
Вот причины такого замедления: ИИИИИИИВ Рождение С++ о время, «впустую» потраченное компилятором С на анализ промежуточного С-кода; о время, необходимое для управления всем этим процессом. Величина накладных расходов зависит в первую очередь от скорости чтения и записи промежуточного С-кода, а это определяется тем, как система управляет доступом к диску.
На протяжении нескольких лет я измерял накладные расходы на различных системах и получил данные, что они составляют от 25 до 100% от «необходимого» времени компиляции. Я видел и такие компиляторы, которые не генерировали промежуточный С-код и все же были медленнее, чем сочетание СГгопг с С. Отмечу, что компилятор С используется только как генератор кода. Любое сообщение от компилятора С свидетельствует об ошибке либо в нем, либо в самом СГгопц по не в исходном тексте С++-программы. Любая синтаксическая или семантическая ошибка в принципе должна отлавливатъся в СГгопс — интерфейсе к компилятору С.
В этом отношении С++ и его компилятор С(гонг отличаются от языков, построенных как препроцессоры типа КагГог ГКегп18Ьап, 1976] и ОЬ1есгьуе С [Сох, 198б1. Я подчеркиваю это, поскольку споры на тему, что такое С1гопц шли в течение длительного времени. Его называли препроцессором, так как он генерирует С-код, а для приверженцев С это было доказательством того, что СГгопс — простенькая программка наподобие макропроцессора. Отсюда делались ложные въ1воды о возможности построчной трансляции С++ в С, о невозможности символической отладки на уровне С++ при нсполъзовании СГгопц о том, что генерируемый СГгопг код должен быть хуже кода, генерируемого «настояшими компиляторами», что С++ — не «настояший язык» и т.д.
Естественно, такие необоснованные обвинения меня раздражали, особенно когда они подавались как критика самого языка С++. В настояшее время несколько компиляторов используют СГгопг с локальными кодогенераторами, не прибегая к помоши С. Для пользователя единственным отличием является более быстрая компиляция. Забавная особенность ситуации заключается в том, что мне не нравится большинство препроцессоров и макрообработчиков. Одна из целей С++ — полностью устранить необходимость в препроцессоре С (см.
раздел 4А и главу 18), поскольку я считаю, что он провоцирует ошибки. В основные задачи, стоявшие перед СГгопц входило обеспечение в С++ рациональной семантики, которую нельзя было реализовать с помошью имевшихся в то время компиляторов С. Компиляторам не хватало информации о типах и областях действия, для того чтобы обеспечить такое разрешение имен, которое было необходимо С++. Язык С++ проектировался в расчете на интенсивное использование технологии компиляции, а не на поддержку во время исполнения или деталъную проработку разрешения имен в выражениях самим программистом (как приходится делать в языках без механизма перегрузки). Следовательно, С++ не мог быть скомпилирован с помошью обычной технологии препроцессирования.