А.В. Ахо, М.С. Лам, Р. Сети, Дж. Д. Ульман - Компиляторы - принципы, технологии и инструментарий (1114947), страница 14
Текст из файла (страница 14)
В программе С на рис. 1.12 идентификатор а представляет собой макрос, который обозначает выражение (г + 1). Но что такое х? Мы не можем разрешить х статически, т.е, в терминах текста программы. №с(еййпе а (х+1) 1пс х = 2 чоЫ Ь() ( №пс х = 1; рг№птй("%Фп", а); но№с( с() ( ргйпсг("ЪФп", а); ) цо№с1 гаа1п() ( Ь(); с(); Рис. 1.12.
Динамические области видимости макросов В действительности для интерпретации х мы должны использовать обычное правило динамической области видимости. Мы рассматриваем все активные в настоящий момент вызовы функций и выбираем из функций, у которых имеет- б7 1.6. Азы языков программирования Аналогия между динамическими и статическими видимостями Хотя может иметься любое количество динамических и статических стратегий для видимостей, существует интересная взаимосвязь между обычным правилом области видимости (для блочной структуры) и обычной динамической стратегией. В определенном смысле динамическое правило — это время, а статическое — пространство.
В то время как статическое правило требует от нас найти объявление, блок которого ближе всего к физическому местоположению использования, динамическое правило требует найти объявление, модуль (вызов процедуры) которого ближе всего по времени использования. ся объявление х, ту, которая была вызвана последней. Именно это объявление х и имеется в виду при использовании х. В примере на рис. 1.12 функция та1л сначала вызывает функцию 6.
При выполнении Ь выводит значение макроса а. Поскольку а должно быть заменено на (х+ 1), мы разрешаем это использование х к объявлению зпк х = 1 в функции 6. Причина такого разрешения в том, что функция 6 содержит объявление х, так что (х+ 1) в вызове ргзпст в функции 6 ссылается на это объявление х. Следовательно, выводимое значение — 2. После завершения 6 и вызова с мы снова сталкиваемся с выводом значения макроса а. Однако единственное имя х, доступное с, — это глобальная переменная х.
Инструкция рхзпг.х в функции с, таким образом, обращается к объявлению глобальной переменной х, и выводимое значение — 3. и Разрешение динамической области видимости существенно также для полиморфных процедур, которые могут иметь два или более определения для одного и того же имени, зависящих только от типов аргументов. В некоторых языках, таких как М(.
(см. раздел 7.3.3), можно статически определить типы для всех применений имен, и в этом случае компилятор в состоянии заменить каждое использование имени процедуры р обращением к коду надлежащей процедуры. Однако в других языках, таких как 1ача и С++, бывают ситуации, когда компилятор не в состоянии решить такую задачу. Пример 1.8. Отличительная особенность объектно-ориентированного программирования заключается в возможности каждого объекта вызывать в ответ на сообщение соответствующий метод. Другими словами, выполняемая процедура х.тп () зависит от класса объекта, обозначенного как х. Вот типичный пример.
1. Имеется класс С с методом т (). 2. Р является подклассом С и имеет собственный метод т (). 68 Глава!. Введение в компиляцию 3. Имеется использование т в виде х.т (), где х — объект класса С. Обычно во время компиляции невозможно определить, принадлежит ли х классу С или подклассу Р. Если метод вызывается несколько раз, весьма вероятно, что у части вызовов х будет принадлежать классу С, в то время как у остальных вызовов х будет принадлежать классу Р. Какое именно определение т должно быть использовано, до выполнения программы выяснить невозможно. Таким образом, код, сгенерированный компилятором, должен определять класс объекта х и вызывать тот или иной метод с именем т. и 1.6.6 Механизмы передачи параметров Все языки программирования имеют понятие процедуры, но они отличаются один от другого способами получения процедурой своих аргументов.
В этом разделе мы рассмотрим, как фактические параметры (параметры, используемые в вызове процедуры) связываются с формальными параметрами (которые используются в определении процедуры). Как вызывающий процедуру код должен работать с параметрами, зависит от того, какой именно механизм передачи параметров применяется при вызове. Подавляющее большинство языков программирования использует либо "передачу по значению", либо "передачу по ссылке", либо оба эти механизма. Мы рассмотрим, что означают эти термины, а также еще один метод — "передачу по имени", который представляет, в первую очередь, исторический интерес.
Передача ~о значеннв При передаче по значению фактический параметр вычисляется (если он представляет собой выражение) или копируется (если это переменная). Полученное значение помещается в местоположение, принадлежащее соответствующему формальному параметру вызываемой процедуры.
Этот метод используется в С и зача и широко применяется в С++, так же, как и во многих других языках программирования. Передача по значению обладает той особенностью, что все вычисления, выполняемые над формальными параметрами вызываемой процедурой, являются локальными для этой процедуры, а сами фактические параметры при этом не могут быть изменены. Заметим, однако, что в С можно передать указатель на переменную, что позволит вызываемой функции изменить ее значение. Аналогично имена массивов, передаваемые в качестве параметров в С, С+~- или лача, по сути дают вызываемой процедуре указатель или ссылку на сам массив.
Таким образом, если а— имя массива в вызывающей процедуре и оно передается по значению соответствующему формальному параметру х, то присваивание наподобие х[х] = 2 в действительности изменяет элемент а [г]. Причина этого в том, что хотя х и по- 69 1.6. Азы языков программирования лучает копию значения а, это значение в действительности является указателем на начало области памяти, в которой располагается массив с именем а.
Аналогично в 1ача многие переменные на самом деле представляют собой ссылки, или указатели, на обозначаемые ими объекты. Это утверждение применимо к массивам, строкам и объектам всех классов. Несмотря на то что в 1ача используется только передача параметров по значению, при передаче имени объекта вызываемой процедуре значение, которое она получает, в действительности представляет собой указатель на объект. Таким образом, вызываемая процедура может влиять на значение самого объекта.
Передача по ссылке При передаче ло ссылке вызываемой процедуре в качестве значения соответствующего формального параметра передается значение адреса фактического параметра. Использование формального параметра в коде вызываемой процедуры реализуется путем следования по этому указателю к местоположению, указанному вызывающим кодом. Изменения формального параметра, таким образом, проявляются как изменения фактического параметра.
Однако если фактический параметр представляет собой выражение, то это выражение вычисляется перед вызовом, а результат вычисления помещается в собственное, отдельное местоположение в памяти. Изменения формального параметра приводят к изменениям значения в этом месте памяти, но никак не затрагивают данные вызывающей процедуры. Передача по ссылке используется для ссылочных параметров в Сч-ь и доступна во многих других языках программирования. Это практически единственный вариант передачи больших обьекгов, массивов или структур. Причина этого заключается в том, что при передаче по значению от вызывающего кода требуется копирование всего фактического параметра в место, принадлежащее соответствующему формальному параметру.
Такое копирование становится слишком дорогостоящим с ростом размера параметра. Как уже отмечалось при рассмотрении передачи параметров по значению, языки программирования, такие как 1ача, решают задачу передачи массивов, строк и других объектов путем копирования ссылок на них. В результате За~а ведет себя так, как если бы для всех типов, отличных от базовых (наподобие целых чисел или чисел с плавающей точкой), использовалась передача параметров по ссылке. Передача по имени Третий механизм — передача по имени — использовался в раннем языке программирования А!яо! 60.
Этот способ требует, чтобы вызываемый код выполнялся, как если бы фактический параметр заменял формальный параметр в коде буквально, т.е. как если бы формальный параметр был макросом, обозначающим фактический параметр (с переименованием локальных имен в вызываемой проце- 70 Глава 1. Введение в компиляцию дуре, чтобы они оставались различными). Если фактический параметр представлял собой выражение, а не переменную, применялось определенное интуитивное поведение кода, что и послужило одной из причин отказа от этого метода передачи параметров в современном программировании.
1.6.7 Псевдонимы Передача параметров по ссылке (или ее имитация, как в случае 1ача, когда по значению передаются ссылки на объекты) имеет одно интересное следствие. Возможна ситуация, когда два формальных параметра будут ссылаться на одно и то же место в памяти; о таких переменных говорят, что они являются псевдонимами (айааеа) друг друга. В результате любые две переменные, которые получают свои значения из двух различных формальных параметров, могут оказаться псевдонимами друг друга.
Пример 1.9. Предположим, что а — массив, принадлежащий процедуре р, и процедура р вызывает процедуру а (х, у) при помощи вызова а (а, а). Предположим также, что параметры передаются по значению, но имена массивов в действительности представляют собой ссылки на местоположения массивов в памяти, как в С или аналогичных языках программирования. Тогда х и у становятся псевдонимами друг друга. Важный момент заключается в том, что если внутри д имеется присваивание к[10] = 2, то значение у [101 также становится равным 2.
о Оказывается, понимание псевдонимов и механизмов, создающих их, очень важно при оптимизации программ компилятором. Как вы увидите в начале главы 9, имеется много ситуаций, когда код может быть оптимизирован только при полной уверенности в том, что определенные переменные не являются псевдонимами. Например, можно определить„что х = 2 — единственное место, где выполняется присваивание переменной х.
Если это так, то можно заменить использование переменной х в программе на 2, например заменить а = х + 3 более простым присваиванием а = 5. Но предположим, что у нас имеется другая переменная, у, являющаяся псевдонимом х. Тогда присванвание у = 4 может вызвать неожиданное изменение значения х. Это также может означать, что замена а = х + 3 присваиванием а = 5 ошибочна и верное значение переменной а должно быть равно 7. 1.6.8 Упражнения к разделу 1.6 Упражнение 1.6.1. Для кода с блочной структурой на языке программирования С, приведенного на рис.