Однопроходный компилятор

редактировать

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

Это относится к логическому функционированию компилятора, а не к фактическому однократному чтению исходного файла. Например, исходный файл можно было один раз прочитать во временное хранилище, но затем эту копию можно было сканировать много раз. Компилятор IBM 1130 Fortran сохранил исходный код в памяти и использовал множество проходов; В отличие от этого ассемблер в системах, в которых отсутствует дисковый накопитель, требовал, чтобы исходная колода карт была дважды представлена ​​устройству чтения / перфорации.

Содержание
  • 1 Свойства
  • 2 Трудности
    • 2.1 Локальный контекст
    • 2.2 Контекст в выражениях
    • 2.3 Контекст среднего диапазона
    • 2.4 Расширения препроцессора
    • 2.5 Контекст дальнего действия
    • 2.6 Неудачные решения
    • 2.7 Однопроходный последовательный ввод, вывод нерегулярной последовательности
  • 3 Объявление перед использованием
    • 3.1 Процедуры и функции
    • 3.2 Пример на языке Паскаль
    • 3.3 Рекурсия препроцессора
    • 3.4 Прямые объявления считаются вредными
  • 4 См. Также
Свойства

Однопроходные компиляторы меньше и быстрее, чем многопроходные компиляторы.

Однопроходные компиляторы не могут создавать такие же эффективные программы, как многопроходные компиляторы, из-за ограниченного объема доступной информации. Многие эффективные оптимизации компилятора требуют нескольких проходов по базовому блоку, циклу (особенно вложенным циклам), подпрограмме или целому модулю. Некоторые требуют пропуска всей программы. Некоторые языки программирования просто не могут быть скомпилированы за один проход из-за их конструкции. Например, PL / I позволяет размещать объявления данных в любом месте программы, в частности, после некоторых ссылок на еще не объявленные элементы, поэтому код не может быть сгенерирован до тех пор, пока не будет просканирована вся программа. Определение языка также включает операторы препроцессора, которые генерируют исходный код для компиляции: обязательно несколько проходов. Напротив, многие языки программирования были разработаны специально для компиляции с использованием однопроходных компиляторов и включают специальные, позволяющие выполнять однопроходную компиляцию.

Трудности

Основная проблема - прямые ссылки. Правильная интерпретация символа в некоторой точке исходного файла может зависеть от наличия или отсутствия других символов далее в исходном файле, и пока они не встретятся, правильный код для текущего символа не может быть получен. Это проблема зависимости от контекста, и диапазон может быть любым, от соседних символов до произвольно больших объемов исходного текста.

Локальный контекст

Предположим, что символ < is recognized as being for a "less than" comparison, as opposed to "greater than" for example. Because of character coding limitations, the glyph ≤ may not be available in a standard encoding, so a compound representation is to be allowed, "<=". Even though this context is determined by the very next symbol, it is unknown when "<" is encountered. Similarly, the symbol "=" does not always mean "=", as when it is a part of a compound symbol. Other compound symbols might include ".lt." for the case when the special character "<" is unavailable. Yet another possibility where a character code for the glyph ¬ ("not") is unavailable is "<>"для" ¬ = "или" не равно "- в некоторых системах используется ~ или! Для ¬ в качестве дополнительных вариантов. Один из подходов - продвигайте сканирование после "<" and on encountering the "=", backtrack. This of course means that there will be two passes over that portion of text, which is to be avoided. For that matter, the source file may come from a device not supporting a go-back-and-reread operation, such as a card reader. Instead of making an early decision that may later have to be undone, the lexical analyser can maintain multiple interpretations rather like the notion of Quantum Superposition, collapsing to a specific choice only on later observing the determining symbol. Notably, COBOL compilers devote a pass to distinguishing between full stops appearing in decimal constants and the full stops that appear at the end of statements. Such a scheme is unavailable to a single-pass compiler.

Аналогично с названиями предметов. Некоторые языки ограничиваются односимвольными именами, поэтому символ «x» как односимвольное имя сильно отличается от символа «x» в имени, таком как «текст» - теперь контекст выходит за пределы непосредственно соседних символов. Задача лексического анализатора - разделить элементы последовательного исходного потока на токены языка. Не просто слова, потому что "<" and "<=" are tokens also. Names typically start with a letter and continue with letters and digits, and perhaps a few additional symbols such as "_". The syntax allowed for specifying numbers is surprisingly complex, for example +3.14159E+0 can be valid. It is usual to allow an arbitrary number of space characters between tokens, and fortran is unusual in allowing (and ignoring) spaces within apparent tokens also so that "GO TO" and "GOTO" are equivalent as are "<=" and "< =". However, some systems may require spaces to delimit certain tokens, and others, such as Python, use leading spaces to indicate the scope of program blocks that otherwise might be indicated by Начало ... Конец или аналогичные маркеры.

Контекст внутри выражений

Языки, допускающие арифметические выражения, обычно следуют синтаксис инфиксной нотации с правилами приоритета. Это означает, что генерация кода для оценки выражения не происходит гладко, поскольку токены выражения извлекаются из исходного текста. Например, выражение x + y * (u - v) не приведет к эквиваленту загрузки x, добавьте y, потому что x не добавляется к y. Если для арифметики используется схема стека, код может начинаться с Load x, но код, соответствующий следующему токену +, не Вместо этого генерируется код для (u - v), за которым следует умножение на y, и только затем добавляется x. Синтаксический анализатор арифметических выражений не перемещается вперед и назад по источнику во время его анализа, он использует локальный стек отложенных операций, управляемых правилами приоритета. Этого танца можно избежать, если потребовать выражения должны быть представлены в обратной польской нотации или аналогичной; для приведенного выше примера что-то вроде u v - y * x +, которое будет сканироваться строго слева направо.

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

a * sin (x) + b * sin (x)

Некоторые языки, такие как Algol, позволяют присваивать значения в арифметических выражениях, поэтому программист мог бы написать что-то вроде

a * (t: = sin (x)) + b * t

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

Контекст среднего диапазона

Хотя лексический анализатор разделил входной поток на поток токенов (и отбросил любые комментарии), интерпретация этих токенов в соответствии с синтаксисом языка может все же зависеть от контекст. Рассмотрим следующие операторы в псевдокоде fortran:

if (выражение) = и т. Д. If (выражение) label1, label2, label3 if (выражение) then

Первое - это присвоение значения некоторого арифметического выражения (и т. д.) в элемент одномерного массива с именем «if». Фортран необычен тем, что не содержит зарезервированных слов, поэтому токен «запись» не обязательно означает, что выполняется инструкция записи. Остальные операторы действительно являются операторами if - второй является арифметическим, если проверяет знак результата выражения и на основании его отрицательного, нулевого или положительного значения переходит к метке 1, 2 или 3; третий - логическое-если, и требует, чтобы результат его выражения был логическим - таким образом, правильная интерпретация лексемы «если», появляющаяся из лексического анализатора, не может быть сделана до тех пор, пока выражение не будет просканировано и не будет следовать за закрывающей скобкой. появляется либо знак равенства, либо цифра (являющаяся текстом label1: fortran использует только целые числа в качестве меток, хотя, если бы буквы были разрешены, сканирование должно было бы полагаться на поиск запятых), либо что-то, начинающееся с буквы (это должно быть "then "), и теперь контекст охватывает произвольный объем исходного текста, потому что выражение является произвольным. Однако во всех трех случаях компилятор может сгенерировать код для оценки выражения по мере продвижения его сканирования. Таким образом, лексический анализ не всегда может определить значение токенов, которые он только что идентифицировал, из-за капризов допустимого синтаксиса, и поэтому синтаксический анализ должен поддерживать суперпозицию возможных состояний, чтобы избежать обратного отслеживания.

Поскольку синтаксический анализ дрейфует в тумане наложенных состояний, в случае возникновения ошибки (т. Е. Обнаружен токен, который не может быть помещен ни в один допустимый синтаксический фрейм) создание полезного сообщения может быть затруднено. Компилятор B6700 Algol, например, был печально известен сообщениями об ошибках, такими как «ожидается точка с запятой» вместе со списком исходной строки и маркером, показывающим местонахождение проблемы, часто отмеченным точкой с запятой. При отсутствии точки с запятой, если она действительно была размещена, как указано, при перекомпиляции для нее вполне могло появиться сообщение «неожиданная точка с запятой». Часто стоит обратить внимание только на первое сообщение об ошибке от компилятора, потому что последующие сообщения пошли наперекосяк. Отменить текущую интерпретацию и затем возобновить сканирование в начале следующего оператора сложно, если исходный файл содержит ошибку, и поэтому последующие сообщения бесполезны. Разумеется, дальнейшая разработка кода прекращена.

Эта проблема может быть уменьшена за счет использования зарезервированных слов, так что, например, «if», «then» и «else» всегда являются частями if-оператора и не могут быть именами переменных, но в результате может оказаться недоступным удивительно большое количество полезных слов. Другой подход - «сглаживание», при котором зарезервированные слова выделяются, например, путем помещения их между специальными символами, такими как точки или апострофы, как в некоторых версиях Алгола. Это означает, что 'if'и if- разные токены, последнее является обычным именем, но предоставление всех этих апострофов вскоре становится утомительным. Для многих языков пробелы предоставляют достаточно информации, хотя это может быть сложно. Часто это не просто пробел (или табуляция и т. Д.), А символ, отличный от буквы или цифры, который завершает возможный текст токена. В приведенном выше примере выражение if-statement должно быть заключено в квадратные скобки, так что "(" определенно завершает идентификацию "if" и аналогично ")" позволяет идентифицировать "then"; кроме того, другие части составного оператора if должны появиться в новых строках: else и end if (или endif) и else if. Напротив, в Algol и других скобках нет необходимости, и все части if-оператора могут находиться в одной строке. В Паскале ifa orb, затем и т. Д. действительно, но если a и b являются выражениями, они должны быть заключены в скобки.

Списки исходных файлов, создаваемые компилятором, можно упростить для чтения, если зарезервированные слова, которые он идентифицирует, представлены подчеркнутыми, жирным шрифтом или курсивом, но это было критика: «Алгол - единственный язык, который различает курсив и нормальную точку». На самом деле это не шутки. В fortran начало оператора do, например DO 12 I = 1,15, отличается от DO 12 I = 1,15(присвоение значения 1,15 переменной с именем DO12I; напомним, что пробелы не имеют значения) только из-за разницы между запятой и точкой, а глифы печатного списка могут быть неправильно сформированы.

Тщательное внимание к дизайну языка может способствовать ясности и простоте выражения с целью создания надежного компилятора, поведение которого легко понять. И все же плохой выбор - обычное дело. Например, Matlab обозначает транспонирование матрицы с помощью апострофа, как в A ', что является безупречным и близко соответствует математическому использованию. Хорошо и хорошо, но для разделителей текстовой строки Matlab игнорирует возможность, предоставляемую символом двойной кавычки для любых целей, и использует для этого апострофы. Хотя Octave использует двойные кавычки для текстовых строк, он также стремится принимать операторы Matlab, и поэтому проблема распространяется на другую систему.

Расширения препроцессора

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

ifусловия, затем этот источник иначе другой источник fi

часто с некоторым расположением, позволяющим отличать утверждения исходного кода препроцессора от "обычных" исходных операторов, например, оператор, начинающийся с символа% в pl / i или # и т. д. Другой простой вариант - это вариант

define this = that

Но необходимо соблюдать осторожность, так как в

определить SumXY = (x + y) sum: = 3 * SumXY;

Так как без скобок результатом будет сумма: = 3 * x + y; Точно так же необходимо соблюдать осторожность при определении границ замещающего текста и того, как будет сканироваться полученный текст. Рассмотрим

#define three = 3; #define point =.; #define один = 1; x: = три десятых;

Здесь оператор define заканчивается точкой с запятой, а сама точка с запятой не является частью замены. Вызов не может быть x: = threepointone;, потому что это другое имя, но три точки одинбудет 3. 1и последующее сканирование может или не может рассматривать это как отдельный токен.

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

Кроме того, один и тот же синтаксис препроцессора может использоваться для нескольких разных языков, даже для естественных языков, как при создании истории из шаблона истории с использованием имени человека, клички, клички собаки и т. Д. и возникает соблазн разработать программу препроцессора, которая будет принимать исходный файл, выполнять действия препроцессора и выводить результат, готовый для следующего этапа, компиляции. Но это явно составляет по крайней мере один дополнительный проход через исходный код, и поэтому такое решение будет недоступно для однопроходного компилятора. Таким образом, продвижение по фактическому исходному файлу ввода может продвигаться урывками, но по-прежнему является однонаправленным.

Контекст дальнего действия

Генерация кода компилятором также сталкивается с проблемой прямой ссылки, наиболее непосредственно в подобной метке, где метка назначения является меткой неизвестное расстояние дальше в исходном файле, и, таким образом, инструкция перехода для достижения местоположения этой метки включает неизвестное расстояние через еще не сгенерированный код. Некоторые языковые конструкции, на которые, возможно, влияет «GOTO, считающиеся вредными», не имеют инструкции GOTO, но это не позволяет избежать проблемы, поскольку в программе есть много неявных эквивалентов GOTO. Рассмотрим

ifусловие, затем код истина иначе код ложь fi

Как упоминалось ранее, код для оценки условия может быть сгенерирован сразу. Но когда встречается токен, затем, должен быть помещен код операции JumpFalse, адрес назначения которого является началом кода для ложных операторов кода, и аналогично, когда токен else является При обнаружении только что завершенного кода для операторов code true должна следовать операция перехода в стиле GOTO, местом назначения которой является код, следующий за концом if-оператора, здесь отмеченный токеном fi . Эти места назначения становятся известными только после генерации произвольного количества кода для еще не просканированного источника. Подобные проблемы возникают для любого оператора, части которого охватывают произвольное количество исходного кода, например, для оператора case .

Компилятор с рекурсивным спуском будет активировать процедуру для каждого типа оператора, такого как оператор if, в свою очередь, вызывая соответствующие процедуры для генерации кода для операторов части кода true и code false части кода его оператор и аналогично для других операторов в соответствии с их синтаксисом. В своем локальном хранилище он будет отслеживать местоположение поля адреса своей неполной операции JumpFalse, и при обнаружении его токена, затем будет размещать теперь известный адрес, и аналогично при обнаружении fi токен для перехода, необходимого после кода true code. Оператор GoTo отличается тем, что код, который нужно перескочить, находится не внутри формы оператора, поэтому требуется запись во вспомогательной таблице «fixups», которая будет использоваться, когда, наконец, встретится ее метка. Это понятие можно было расширить. Все переходы к неизвестным адресатам могут быть сделаны через запись в таблице переходов (адреса которой позже заполняются по мере обнаружения адресатов), однако необходимый размер этой таблицы неизвестен до конца компиляции.

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

Неудачные решения

Хотя в приведенном выше описании использовалось понятие о том, что код может быть сгенерирован с определенными полями, которые предстоит исправить позже, существовало неявное предположение, что размер таких кодовых последовательностей был стабильный. Это может быть не так. На многих компьютерах предусмотрены операции, занимающие разные объемы памяти, в частности относительную адресацию, при которой, если пункт назначения находится в пределах, скажем, -128 или +127 шагов адресации, тогда можно использовать восьмибитное адресное поле, в противном случае требуется гораздо большее адресное поле для достижения. Таким образом, если код был сгенерирован с предполагаемым коротким адресным полем, позже может возникнуть необходимость вернуться и настроить код для использования более длинного поля, в результате чего более ранний код, ссылающийся на местоположения после изменения, также придется скорректировать. Аналогичным образом, более поздние ссылки, идущие в обратном направлении через изменение, должны быть исправлены, даже те, которые были по известным адресам. Кроме того, сама информация об исправлении должна быть исправлена ​​правильно. С другой стороны, длинные адреса могут использоваться во всех случаях, когда близость не определена, но результирующий код больше не будет идеальным.

Однопроходный последовательный ввод, вывод нерегулярной последовательности

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

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

Объявление перед использованием

При генерации кода для различных выражений компилятор должен знать природу операндов. Например, такой оператор, как A: = B; может создавать довольно разный код в зависимости от того, являются ли A и B целыми числами или переменными с плавающей запятой (и какого размера: одинарная, двойная или учетверенная точность) или комплексными числами, массивами, строками, типами, определяемыми программистом и т. д. Простым подходом было бы передать подходящее количество слов для хранения, но для строк это может быть неподходящим, так как получатель может быть меньше, чем поставщик, и в любом случае может использоваться только часть строки - возможно, в ней есть место для тысячи символов, но в настоящее время содержит десять. Затем есть более сложные конструкции, предлагаемые COBOL и pl / i, такие как A: = B по имени;В этом случае A и B являются агрегатами (или структурами), где A, например, имеет части Ax, Ayи A. другие, а B имеет части By, Bcи Bx, и именно в таком порядке. Функция «по имени» означает эквивалент A.y: = B.y; A.x: = B.x;Но поскольку B.cне имеет аналога в A, а A. другойне имеет аналога в B, они не участвуют.

Все это можно решить с помощью требования о том, чтобы элементы декларировались до их использования. Некоторые языки не требуют явного объявления, генерируя неявное объявление при первом обнаружении нового имени. Если компилятор fortran обнаруживает ранее неизвестное имя, первая буква которого является одной из I, J,..., N, тогда переменная будет целым числом, иначе - переменной с плавающей запятой. Таким образом, имя DO12Iбудет переменной с плавающей запятой. Это удобно, но после нескольких опытов с ошибками в именах большинство программистов соглашаются с тем, что следует использовать параметр компилятора «неявное отсутствие».

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

ifусловие затем pi: = "3.14" else pi: = 3.14 fi; печать пи;

Если существует компилятор для такого языка, он должен будет создать сложную сущность для представления переменной pi, содержащую указание на то, что это за текущий тип и связанное хранилище для представления такого типа. Это, безусловно, гибко, но может оказаться бесполезным для интенсивных вычислений, таких как решение A.x = b, где A - матрица порядка сотни, и внезапно любой из ее элементов может быть другого типа.

Процедуры и функции

Объявление перед использованием также является простым требованием для выполнения процедур и функций, и это относится также к вложению процедур в процедуры. Как и в случае с ALGOL, Pascal, PL / I и многими другими, MATLAB и (с 1995 г.) Fortran позволяют функции (или процедуре) содержать определение другой функции (или процедуры), видимой только внутри содержащей функцию, но эти системы требуют чтобы они были определены после окончания содержащей процедуры.

Но когда разрешена рекурсия, возникает проблема. Две процедуры, каждая из которых вызывает другую, нельзя одновременно объявить перед использованием. Один должен быть первым в исходном файле. Это не имеет значения, если, как при встрече с неизвестной переменной, из встречи можно вывести достаточно, чтобы компилятор мог сгенерировать подходящий код для вызова неизвестной процедуры, с, конечно же, устройством "исправления", чтобы вернуться. и введите правильный адрес назначения, когда встретится определение процедуры. Например, это относится к процедуре без параметров. Результат, возвращаемый при вызове функции, может иметь тип, различимый при вызове, но это не всегда может быть правильным: функция может возвращать результат с плавающей запятой, но имеет значение, присвоенное целому числу.

Паскаль решает эту проблему, требуя «предварительного объявления». Одно из объявлений процедуры или функции должно быть указано первым, но вместо тела процедуры или функции дается ключевое слово forward . Затем можно объявить другую процедуру или функцию и определить ее тело. В какой-то момент повторно объявляется процедура или функция «вперед» вместе с телом функции.

Для вызова процедуры (или функции) с параметрами их тип будет известен (они объявляются перед использованием), но их использование в вызове процедуры может не быть. Фортран, например, передает все параметры по ссылке (то есть по адресу), поэтому нет немедленных трудностей с генерацией кода (как всегда, с фактическими адресами, которые будут исправлены позже), но Паскаль и другие языки позволяют передавать параметры разными методами. по выбору программиста (по ссылке, или по значению, или даже, возможно, по «имени» ), и это обозначается только в определении процедуры, который неизвестен до того, как определение было встречено. Конкретно для Паскаля в спецификации параметров префикс «Var» означает, что он должен быть получен по ссылке, его отсутствие означает по значению. В первом случае компилятор должен сгенерировать код, который передает адрес параметра, а во втором он должен сгенерировать другой код, который передает копию значения, обычно через стек. Как всегда, для решения этой проблемы может быть задействован механизм «исправления», но это будет очень запутанно. Многопроходные компиляторы, конечно, могут сопоставлять всю необходимую информацию, когда они перемещаются туда и обратно, но однопроходные компиляторы не могут. Генерация кода может быть приостановлена, пока сканирование продвигается (и его результаты будут храниться во внутренней памяти) до тех пор, пока не будет обнаружена необходимая сущность, и это не может рассматриваться как приводящее ко второму проходу через источник, потому что этап генерации кода будет скоро догонял, он просто на время останавливался. Но это было бы сложно. Вместо этого вводится специальная конструкция, в соответствии с которой определение использования параметра в процедуре объявляется «впереди» его более позднего полного определения, чтобы компилятор мог знать его перед использованием, как это требуется.

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

Требование, которое должно быть выполнено, заключается в том, что в текущей точке однопроходной компиляции необходима информация об объекте, чтобы правильный код для него мог быть создан сейчас, если с исправлением адреса позже. Будет ли требуемая информация обнаружена позже в исходном файле или в каком-то отдельно скомпилированном файле кода, здесь информация предоставляется некоторым протоколом.

Проверяются ли все вызовы процедуры (или функции) на совместимость друг с другом и их определения - это отдельный вопрос. В языках, порожденных Алголообразным вдохновением, эта проверка обычно тщательна, но другие системы могут быть безразличными. Не говоря уже о системах, которые позволяют процедуре иметь необязательные параметры, ошибки в количестве и типе параметров обычно приводят к сбою программы. Системы, которые допускают раздельную компиляцию частей полной программы, которые впоследствии «связываются» вместе, должны также проверять правильный тип и количество параметров и результатов, поскольку ошибки еще легче сделать, но часто этого не происходит. Некоторые языки (например, Algol) имеют формальное понятие «обновление», «расширение» или «продвижение», посредством чего процедура, которая ожидает, скажем, параметра двойной точности, может быть вызвана с ним как с переменной одинарной точности, и в этом случае компилятор генерирует код, который сохраняет переменную одинарной точности во временной переменной двойной точности, которая становится фактическим параметром. Однако это изменяет механизм передачи параметров на копирование и копирование, что может привести к незначительным различиям в поведении. Гораздо менее тонкими являются последствия, когда процедура получает адрес переменной с одинарной точностью, когда она ожидает параметр двойной точности или другие варианты размера. Когда в рамках процедуры считывается значение параметра, будет прочитано больше памяти, чем у данного параметра, и полученное значение вряд ли будет улучшением. Гораздо хуже, когда процедура меняет значение своего параметра: обязательно что-то будет повреждено. На поиск и исправление этих упущений можно потратить немало терпения.

Пример Pascal

Примером такой конструкции является объявление forward в Pascal. Паскаль требует, чтобы процедуры были объявлены или полностью определены перед использованием. Это помогает однопроходному компилятору с проверкой типа : вызов процедуры, которая нигде не объявлена, является явной ошибкой. Прямые объявления помогают взаимно рекурсивным процедурам вызывать друг друга напрямую, несмотря на правило объявления перед использованием:

function odd (n: integer): boolean; begin, если n = 0, тогда odd: = false else if n < 0 then odd := even(n + 1) { Compiler error: 'even' is not defined } else odd := even(n - 1) end; function even(n : integer) : boolean; begin if n = 0 then even := true else if n < 0 then even := odd(n + 1) else even := odd(n - 1) end;

Добавив опережающее объявление для функции evenперед функцией odd, Компилятору -pass сообщается, что позже в программе будет определение и даже.

функция даже (n: целое число): логическое; вперед; функция odd (n: целое число): логическое; {Et cetera}

При фактическом объявлении тела функции параметры либо опускаются, либо должны быть полностью идентичны исходному прямому объявлению, либо будет отмечена ошибка.

Предпроцессорная рекурсия

При объявлении сложных агрегатов данных могло возникнуть возможное использование функций Odd и Even. Возможно, если агрегат данных X имеет размер хранилища, равный нечетному количеству байтов, к нему можно добавить однобайтовый элемент под контролем теста на Odd (ByteSize (X)), чтобы получить четное число. Учитывая эквивалентные объявления Odd и Even, как указано выше, «прямое» объявление, вероятно, не понадобится, потому что использование параметров известно препроцессору, который вряд ли предоставит возможность выбора между ссылкой и значением. Однако вызов этих функций в исходном коде (вне их определений) может быть только после их фактического определения, поскольку требуется, чтобы результат вызова был известен. Если, конечно, препроцессор не задействовал несколько проходов исходного файла.

Прямые объявления считаются вредными

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

См. Также
Последняя правка сделана 2021-06-01 11:43:45
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте