Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 [ 71 ] 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

появляться в тех же самым местах что и простые скалярные идентификаторы, вы не сможете сказать об их различиях. Вы должны вернуться к объявлениям, чтобы выяснить это. Некоторые люди полагают, что это преимущество. Их аргументы в том, что идентификатор замещается значением и почему вас заботит, сделано ли это с помощъю подстановки или функции? Но нас это иногда заботит, потому что функция может выполняться довольно долго. Если написав простой идентификатор в данном выражении мы можем понести большие затраты во время выполнения, то мне кажется, что мы должны быть осведомлены об этом.

В любом случае, Никлаус Вирт разработал и Pascal и Modula-2. Я оправдаю его и полагаю что он имел веские причины для изменения правил во втором случае!

Само собой разумеется, легко принять любую точку зрения на то, как разрабатывать язык, так что это строго вопрос персонального предпочтения. Делайте это таким способом, какой вам больше нравится.

Перед тем как пойти дальше, давайте изменим транслятор для поддержки списка параметров (возможно пустого). Пока мы не будем генерировать никакого дополнительного кода... просто анализировать синтаксис. Код для обработки объявления имеет ту же самую форму, которую мы видели раньше когда работали со списками переменных:

{ Process the Formal Parameter List of a Procedure }

procedure FormalList;

begin

Match(();

if Look <> ) then begin FormalParam;

while Look = , do begin Match(,); FormalParam;

end;

end;

Match());

end;

В процедуру DoProc необходимо добавить строчку для вызова FormalList:

{ Parse and Translate a Procedure Declaration } procedure DoProc; var N: char; begin

Match(p); N := GetName; FormalList; Fin;

if InTable(N) then Duplicate(N);

ST[N] := p; PostLabel(N); BeginBlock; Return;

end;

Сейчас код для FormalParam всего лишь пустышка, который просто пропускает имена переменных:



procedure FormalParam; var Name: char; begin

Name := GetName;

end;

Для фактического вызова процедуры должен быть аналогичный код для обработки списка фактических параметров:

{ Process an Actual Parameter } procedure Param; var Name: char; begin

Name := GetName;

end;

{ Process the Parameter List for a Procedure Call }

procedure ParamList;

begin

MatchCC);

if Look <> ) then begin Param;

while Look = , do begin MatchC,); Param;

end;

end;

Match());

end;

{ Process a Procedure Call } procedure CallProc(Name: char); begin

ParamList; Call(Name);

end;

Обратите внимание, что CallProc больше не является просто простой подпрограммой генерации кода. Она имеет некоторую структуру. Для обработки я переименовал подпрограмму генерации кода в просто Call и вызвал ее из CallProc.

Итак, если вы добавите весь этот код в ваш транслятор и протестируете его, вы обнаружите, что действительно можете правильно анализировать синтаксис. Обращаю ваше внимание на то, что здесь нет никакой проверки того, что количество (и, позднее, тип) формальных и фактических параметров совпадает. В промышленном компиляторе, мы конечно должны делать это. Сейчас мы игнорируем эту проблему той причине, что структура нашей таблицы идентификаторов пока не дает нам места для сохранения необходимой информации. Позднее мы подготовим место для этих данных и тогда сможем работать с этой проблемой.

СЕМАНТИКА ПАРАМЕТРОВ

До этого мы имели дело с синтаксисом передачи параметров и получили механизм синтаксического анализа для его обработки. Сейчас мы должны рассмотреть семантику, т. е. действия, которые должны быть предприняты когда мы столкнемся с параметрами. Это ставит нас перед вопросом выбора способа передачи параметров.

Существует более чем один способ передачи параметров и способ, которым мы



сделаем это, может иметь глубокое влияние на характер языка. Так что это одна из тех областей, где я не могу просто дать вам свое решение. Скорее, было бы важно чтобы мы потратили некоторое время на рассмотрение альтернатив, так чтобы вы могли, если захотите, пойти своим путем.

Есть два основных способа передачи параметров:

• По значению

• По ссылке (адресу)

Различия лучше всего видны в свете небольшого исторического обзора.

Старые компиляторы Фортрана передавали все параметры по ссылке. Другими словами, фактически передавался адрес параметра. Это означало, что вызываемая подпрограмма была вольна и считывать и изменять этот параметр, что часто и происходило, как будто это была просто глобальная переменная. Это был фактически самый эффективный способ и он был довольно простым, так как тот же самый механизм использовался во всех случаях с одним исключением, которое я кратко затрону.

Хотя имелись и проблемы. Многие люди чувствовали, что этот метод создавал слишком большую связь между вызванной и вызывающей подпрограммой. Фактически, это давало подпрограмме полный доступ ко всем переменным, которые появлялись в списке параметров.

Часто нам не хотелось бы фактически изменять параметр а только использовать его как входные данные. К примеру, мы могли бы передавать счетчик элементов в подпрограмму и хотели бы затем использовать этот счетчик в цикле DO. Во избежание изменения значения в вызываемой программе мы должны были сделать локальную копию входного параметра и оперировать только его копией. Некоторые программисты на Фортране фактически сделали практикой копирование всех параметров, исключая те, которые должны были использоваться как возвращаемые значения. Само собой разумеется, все это копирование победило добрую часть эффективности, связанной с этим методом.

Существовала, однако, еще более коварная проблема, которая была в действительности не просто ошибкой соглашения "передача по ссылке", а плохой сходимостью нескольких решений реализации.

Предположим, у нас есть подпрограмма:

SUBROUTINE FOO(X, Y, N)

где N - какой-то входной счетчик или флажок. Часто нам бы хотелось иметь возможность передавать литерал или даже выражение вместо переменной, как например:

CALL FOO(A, B, J + 1)

Третий параметр не является переменной, и поэтому он не имеет никакого адреса. Самые ранние компиляторы Фортрана не позволяли таких вещей, так что мы должны были прибегать к ухищрениям типа:

K = J + 1

CALL FOO(A, B, K)

Здесь снова требовалось копирование и это бремя ложилось на программистов. Не хорошо.

Более поздние реализации Фортрана избавились от этого, разрешив использовать выражения как параметры. Что они делали - назначали сгенерированную компилятором переменную, сохраняли значение выражения в этой переменной и затем предавали адрес выражения.

Пока все хорошо. Даже если подпрограмма ошибочно изменила значение анонимной переменной, кто об этом знал или кого это заботило? При следующем вызове она в



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 [ 71 ] 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97