Т. Пратт, М. Зелковиц - Языки программирования - разработка и реализация (4-е издание_ 2002) (1160801), страница 109
Текст из файла (страница 109)
В конце этой цепи связей находится указатель на запись активации главной программы. Эта цепь связей называется динамической цепьк>, так как она связывает вместе активации подпрограмм в порядке их дина>аичсского создания во время выполнения программы. (В разделе 9.4.2 обсуждается близкое понятие — статическая цепь, которая связывает вместе записи активации в целях получения ссылок,) Аппаратная часть традипионного компьютера иногда предоставляет некоторук> поддержку для рассмотренной организации с центральным стеком, но, как правило, ее используют для реализации чего-нибудь более дорогостоящего, чем реализация простой структуры вызова-возврата без рекурсии, Не представляет сложности использовать подпрограммы, реализованные этим простым способом, совместно с подпрограммами, использующими центральный стек, при условии, что компилятор умеет различать эти подпрограммы при компиляции команд са11 и гебвгп.
Только те подпрограммы, которые действительно вызываются рекурсивно, нуждаются в реализации с использованием центрального стека. Так, в некото- 9.1. Управление последовательностью подпрограмм 395 рых языках, например в Р(./1, подпрограммы, вызываемые рекурсивпо, должны помечаты я как ВЕСОВ)НЕ в своих объявлениях; в других языках, подобных С и Разса1, всегда подразумевается рекурсивная структура. 9.1.3. Объявление 1ог~магд в языке Рааса! Рекурсия в вызове процедур создает проблему при использовании стратегии одного прохода, применяемой прн разработке многих компиляторов Рааса!.
Пусть, например, А и В являются подпрограммами и Я вызывает В, а В вызывает А. Тогда, если определение подпрограммы А появляется перед определением В, то естественно, что вызов подпрограммы В в А появляется до определения подпрограммы В. Поменяв местами определения подпрограмм А и В, мы не решим эту проблему, а лишь обратим ее. Эта проблема решается в Рааса!, если использовать для подпрограммы, определяемой в последщою очередь, обьяоление толкал<1, имеющее вид сигнатуры подпрограммы, включая полный список параметров, за которым вместо тела подпрограммы следует слово 1'огнаг0.
В качестве примера можно привести следующее объявление; ргосеннге Я11огиа1 рагаяевог 1мш: вогнало'; С помощью такого опережающего объявления для подпрограммы А подпрограмма В может быть определена полностью, а затем дается полное определение тела подпрограммы А (но список формальнаях параметров не повторяется). Опережающее обьявление подпрограммы А с использованием ключевого слова Гогнагб дает компилятору достаточно информации, чтобы корректно компилировать вызов Я, содержащийся в подпрограмме В, хотя полное описание подпрограммы А еше не появилось. Это тот же принцип, который используе~ся в спецификации пакетов Аг(а (расЕа9е), где сигнатура подпрограммы дается без деталей ее реализации. Поскольку список параметров не должен повторяться, когда подпрограмма позже определяется, его отсутствие является основным источником ошибок программированияя.
В этом случае рядом с листингом тела подпрограммы мы нигде пе обнаружим документацшо сигнатуры подпрограммы. Неправильно используемые параметры, а также некорректно используемые передаваемые по имени и ссылке параметры в теле подпрограммы могут вызвать серьезные проблемы. Одним из способов избежать их является включение в определение подпрограммы комментария, содержащего список параметров. Поэтому после опережающего объявления подпрограммы с помощью спецификации Еогнагд фактическое определение тела подпрограммы может быть дано в следующем виде: ргосееоге Я 11списои фориалвиык параиетров)1: Ьевип епо Причина того, что в языке Разса1 так акцентируется необходимость определения идентификатора до его использования, кроется в ошибочном мнении, что для аффективной компиляции необходим однопроходный компилятор.
За один проход по исходному тексту программы такой компилятор получает весь спектр необходимой информации, считывая и обрабатывая определение одной подпрограммы за один раз и генерируя выполняемую объектную программу, как только он ЗЯВ Глава 9. Управление подпрограммами завершил чтение подпрограммы.
Для обработки программы за один проход компилятор в каждой точке программы должен иметь достаточно информации о значении каждого идентификатора, чтобы сгенерировать корректный объектный код. Как говорилось в главе 2, для уменьшения времени компиляции не обязательно использовать однопроходные компиляторы, а сегодня при наличии дешевых и быстрых микропроцессоров время компиляции больше не является столь значимой проблемой.
Листинг 9. 1. Аномалия с опережающим объявлением подпрограммы в Рааса! с использованием спецификации Тогууагб ргодгав апопа1уш про!.он!рос!: ргосееоге 5; (!) Ьед!п нг!Ге1пСнгопд опе'! епе. ргосееоге Т: (заеса пропущено ргосеоиге 5: гогеаггн ) ргосееоге О. Ьед!и 5 (2) епе ргосееиге 5. (3) Ьед!и нг!Ье1пппдП! опе'! епп. Ьед!и Ь епо: Ьед!и Т епе.
Правило, касающееся употребления конструкции Тогнагб в Рааса), приводит к странной аномалии, которая проиллюстрирована в и рограмм е апппа 1 у (листинг 9.1). Возможны три различных интерпретации этой программы. 1. Компиляция не будет выполнена, потому что программа апппа1у неправильна. Вызов процедуры 5 в точке (2) вызывает процедуру 5, определенную в точке (3), что является опережающей ссылкой без декларации !огне го. 2. Вызов процедуры 5 в точке (2) вызывает процедуру 5, определенную в точке (!), — как раз то, что сделал бы однопроходный компилятор, хотя это неправильная процедура 5 в контексте области видимости этого вызова процедуры.
3. Программа выполняется, вызывая в точке (2) процедуру 5, определенную в точке (3). Это правильная процедура, которую можно вызывать, даже если требуемая, хотя и избыточная, спецификация Тогнагс отсутствует. Внимательное чтение стандарта языка Рааса) (70) поможет определить правильную интерпретацию. 1. Раздел 6.2.2.1 стандарта утверждает, что определяющей точкой для 5(3) является второй оператор ргосесцге 5. 2. Разделы 6 2.2 2 и 6 2 2 3 определяют область для определяющей точки 5(3) как всю процедуру Т. Следовательно, вызов 5(2) является вызовом 5(3), а не 5(!).
9.2. Атрибуты управления данными 397 3. Раздел б.2.2.9 требует, чтобы определяющая точка (например, оператор 5(3) ) для любого фактического идентификатора (напр имер, 5(2) ) появлялась ран ьше его использования, что неявно подразумевает необходимость использования объявления 1огиаг1).
Имея такое подробное объяснение, мы легко приходим к выводу, что первая интерпретация, очевидно, правильна, в то время как третья, хотя и неверна, является обоснованной интерпретацией. Вторая интерпретапия, очевидно, неверна и является наихудшим вариантом из трех. После выполнения этой программы с помошыо тринадцати различных трансляторов языка Разса1 от нескольких фирм-поставщиков для различных компьютеров и операционных систем, включая РС, Мас(пгоз)1, рабочие станции ()1ч1Х, были получены следующие результаты: 3 компилятора 7 компиляторов 3 компилятора Интерпретация 1 (правильная) Интерпретация 2 (худший вариант) Интерпретация 3 (неправильная, но обоснованная) 9.2. Атрибуты управления данными Управлентте данными в языках программирования связано с организацией доступа к данным во время выполнения программы в различных ее точках.
Механизмы управления последовательностью действий, описанные в предыдущей главе, предоставляют средства координирования последовательности выполнения операций во время выполнения программы Когда доходит очередь до какой-либо операции, ей требуется предоставить данные, над которыми она выполняет определенные действия. Возможности управления данными в языке программирования определяют, каким образом могут быть предоставлены необходимые для выполнения операции данные и как результат выполнения одной операции может быть сохранен, а позже использован в качестве операнда следующей операции. При написании программы программист обычно хоро1ио представляет себе, какие операции и в какой последовательности должны быть выполнены в программе, но гораздо реже он может сказать то же самое об операндах этих операций.
Например, в программе на языке С может содержаться следующий оператор: Х:-7+2*1. Очевидно, что лишь незначительное меньшинство следовало стандарту, а бал ьше половины продавцов выбрали необоснованную иптерпретаци1о (по понятным причинам мы не раскрьпгаем здесь их имена). (См. задачу 22 в конце этой главы.) Этот странный пример можно еше несколько усложнить. Если вернуться к вопросам стандартизации языков, обсужлавшимся в разделе 1.3.3, то можно прийти к выводу, что все 13 компиляторов действовали фактически в соответствии со стандартом.
Дело в том, что компилятор должен обрабатывать в соответствии со стандартом те программы, которые сами по себе являются соответствующими стандарту (а данная программа к ним пе относится), поэтому в данном случае для компилятора не существует определенных указаний, что ему делать. Следовательно, с точки зрения соответствия стандарту все три интерпретации верны. 398 Глава 9. Управление подпрограммами Простой анализ показывает, что в нем последовательно выполняются три операции: ул1ножение, сложение и присваивание.
Но что можно сказать об операндах этих операций? Ясно, что одним из операндов операции умножения является число 2, но остальные операнды только обозначены идентификаторами Х, У и 2, которые, очевидно, сами по себе не являются операндами, а лишь некоторым образом указывают на операнды. Идентификатор у может обозначать вещественное или целое число нли имя подпрограммы, не имеющей параметров, выполнение которой приводит к вычислени|о операнда, Возможно, программист допустил ошибку и у на самом деле обозначает булеву величину, или строку символов, или вообще служит меткой какого-либо оператора. 1' может обозначать как некоторую величину, вычисленную недавно, например в предыдущем операторе, так и величину, вычисленную гораздо раньше и отделенную от операции присваивания, где используется т, многими уровнями вызовов подпрограмм.