|
Программирование >> Полиморфизм без виртуальных функций в с++
доказало, что С++ может быть эффективным языком на ПК, и разработчикам коммерческого программного обеспечения есть чем заняться. Размышляя над четвертым вопросом, я пришел к следующим выводам. В языке не должно быть средств, требующих особо изощренного компилятора или поддержки во время исполнения, надо использовать существующие компоновщики, а сгенерированный код должен быть эффективен (по сравнению с С), начиная с самых первых версий. 3.3. Компилятор Cfront Компилятор Cfront для языка С84 был спроектирован и реализован в период между весной 1982 г. и летом 1983 г. Первый пользователь за пределами Центра исследований по вычислительной технике, Джим Коплиеп (Jim Coplien), получил копию в июле 1983 г. Джим работал в фуппе, занимавшейся экспериментальными исследованиями в области коммутации сетей, в отделении Bell Labs в Напервилле, штат Иллинойс. До этого Коплиеи некоторое время пользовался С with Classes. За указанный период я спроектировал С84, написал черновик справочного руководства (опубликованного 1 января 1984 г. [Stroustrup, 1984]), спроектировал библиотеку complex для работы с комплексными числами, которую мы реализовали вместе с Леони Роуз (Leonie Rose) [Rose, 1984], спроектировал и реализовал вместе с Джонатаном Шопиро первый класс string для работы со строками, занимался сопровождением и переносом на другие платформы С with Classes, поддерживал пользователей С with Classes и готовил их к переходу на С84. В общем, беспокойные выдались полтора года. Cfront был (и остается) традиционным компилятором неполного цикла (front-end compiler). Он проверяет синтаксис и семантику языка, строит внутреннее представление профаммы, анализирует и переупорядочивает его и на выходе генерирует файл, подаваемый какому-нибудь генератору кода. Внутреннее представление -это фаф, в котором есть по одной таблице символов на каждую область действия. Общая стратегия компиляции такова: читать по одному глобальному объявлению из исходного файла и формировать вывод только тогда, когда объявление полностью проанализировано. На практике это означает, что компилятору нужно достаточно памяти, чтобы хранить представление всех глобальных имен, типов, а также полный фаф одной функции. Несколькими годами позже я выполнил ряд измерений работы Cfront и обнаружил, что потребление памяти стабилизируется на уровне 600 Кб на DEC VAX практически независимо от компилируемой программы. Поэтому в 1986 г. мне удалось выполнить перенос Cfront на PC/AT. К моменту выхода версии 1.0 в 1985 г. Cfront представлял собой примерно 12 тыс. строк кода на С++. Организация Cfront довольно обычна, разве что в нем используется много таблиц символов вместо одной. Первоначально Cfront был написан на С with Classes (а на чем же еще?), но скоро был переписан на С84, так что самый первый работающий компилятор C+-I- написан на С++. Даже в первой версии Cfront интенсивно использовались классы и наследование. Однако виртуальных функций в нем не было. Cfront - это компилятор неполного цикла. В реальных проектах его одного было недостаточно. Ему необходим драйвер, который пропускает исходный файл через препроцессор С (Срр), зате.м подает выход Срр на вход Cfront, а выход Cfront - на вход компилятора С (см. рис. 3.1). Кроме того, драйвер должен был обеспечить дина.мическую (во время исполнения) инициализацию. В Cfront 3.0 драйвер стал еще более сложным, поскольку было реализовано автоматическое инстанцирование (см. раздел 15.2) шаблонов [McCluskey, 1992]. Исходный текст cfront Объектный код Рис. 3.1 3.3.1. Генерирование С-кода Самый необычный для своего времени аспект Cfront состоял в том, что он генерировал С-код. Это вызвало много путаницы. Cfront генерировал С-код, поскольку первая реализация должна была быть хорошо переносимой, а я считал, что более переносимого ассемблера, чем С, не найти. Я легко мог бы сгенерировать внутренний формат для кодогенератора или вывести файл на языке ассемблера, но это было не нужно моим пользователям. С помощью ассемблера и внутреннего кодогенератора невозможно было бы обслужить более четверти всех пользователей, и ни при каких обстоятельствах я не смог бы написать, скажем, шесть кодогенераторов, чтобы покрыть запросы 90% пользователей. Поэтому я решил, что использование С в качестве внутреннего формата - это разумный выбор. Позже построение компиляторов, генерирующих С-код, стало распространенной практикой. Так были реализованы языки Ada, Eiffel, Modula-3, Lisp и Smalltalk. Я получил высокую переносимость ценой небольшого увеличения времени компиляции. Вот причины такого замедления: □ время, требующееся Cfront для записи промежуточного С-кода; □ время, нужное компилятору С, чтобы прочитать про.межуточный С-код; □ время, впустую потраченное ко.мпилятором С на анализ промежуточного С-кода; □ время, необходимое для управления всем этим процессом. Величина накладных расходов зависит в первую очередь от скорости чтения и записи промежуточного С-кода, а это определяется тем, как система управляет доступом к диску. На протяжении нескольких лет я измерял накладные расходы на различных системах и получил данные, что они составляют от 25 до 100% от необходимого времени компиляции. Я видел и такие компиляторы, которые не генерировали промежуточный С-код и все же были медленнее, чем сочетание Cfront с С. Отмечу, что компилятор С используется только как генератор кода. Любое сообщение от компилятора С свидетельствует об ошибке либо в нем, либо в самом Cfront, по не в исходно.м тексте С++-програм.мы. Любая синтаксическая или семантическая ошибка в принципе должна отлавливаться в Cfront - интерфейсе к компилятору С. В этом отношении С++ и его компилятор Cfront отличаются от языков, построенных как препроцессоры типа Ratfor [Kernighan, 1976] и Objective С [Сох, 1986]. Я подчеркиваю это, поскольку споры на тему, что такое Cfront, шли в течение длительного времени. Его называли препроцессором, так как он генерирует С-код, а для приверженцев С это было доказательством того, что Cfront - простенькая программка наподобие макропроцессора. Отсюда делались ложные выводы о возможности построчной трансляции С++ в С, о невозможности символической отладки на уровне С++ при использовании Cfront, о том, что генерируемый Cfront код должен быть хуже кода, генерируемого настоящими компиляторами , что С++ - не настоящий язык и т.д. Естественно, такие необоснованные обвинения меня раздражали, особенно когда они подавались как критика самого языка С++. В настоящее время несколько компиляторов используют Cfront с локальными кодогенераторами, не прибегая к помощи С. Для пользователя единственным отличием является более быстрая компиляция. Забавная особенность ситуации заключается в том, что мне не нравится большинство препроцессоров и макрообработчиков. Одна из целей С++ - полностью устранить необходимость в препроцессоре С (см. раздел 4.4 и главу 18), поскольку я считаю, что он провоцирует ошибки. В основные задачи, стоявшие перед Cfront, входило обеспечение в С++ рациональной семантики, которую нельзя было реализовать с помощью имевшихся в то время компиляторов С. Компиляторам не хватало информации о типах и областях действия, для того чтобы обеспечить такое разрешение имен, которое было необходимо С++. Язык С++ проектировался в расчете на интенсивное использование технологии компиляции, а не на поддержку во время исполнения или детальную проработку разрешения имен в выражениях самим программистом (как приходится делать в языках без механизма перегрузки). Следовательно, C-I-+ не мог быть скомпилирован с помощью обычной технологии препроцессирования. В свое время я рассмотрел и отверг такие альтернативы семантике языка и технологии компиляции. Непосредственный предшественник С++ - Срге - являлся довольно традиционным препроцессором.
|
© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |