лекция 16 (Языки программирования (лекции) (2008))
Описание файла
Файл "лекция 16" внутри архива находится в папке "Языки программирования (лекции) (2008)". Документ из архива "Языки программирования (лекции) (2008)", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Онлайн просмотр документа "лекция 16"
Текст из документа "лекция 16"
Языки программирования.
Лекция 16.
Глава 6: Раздельная трансляция
Вопрос тесно связан с понятием физического модуля.
Виды трансляций:
1. Пошаговая трансляция (программа разбивается на естественные куски-строки, которые передаются кусочками в транслятор и сразу же выполняются, если он не мог выполнить, то ждал следующий кусок) - Basic;
2. Инкрементная трансляция (сам компилятор без вмешательства программиста определяет, что за кусочки нужны и без передаёт их транслятору)- современные отладчики, при незначительных изменениях в коде. ;
3. Цельная трансляция (программа целиком поступает на вход транслятору) - любой скриптовый язык ,
компилируемые ЯП: программа из одного языка Л1 переводится в язык Л2(объектный язык), который уже интерпретируется ( язык JVM).
Цельные компилируемые языки (языки, созданные не для индустриального программирования)- Алгол-60(для обмена алгоритмами), стандартный Паскаль(для обучения программирования).
4. Языки с раздельной трансляцией - программа разбивается на модули, возникает понятие физического модуля (ФМ) или единицы компиляции, т.е. при раздельной трансляции используются компилируемые яп.
В компилируемых яп чётко разделяются 2 фазы: трансляция и сборка программы (после которой образуется исполняемый код).
Уже в Фортране была реализована возможность раздельной трансляции (РТ). Все процедуры и ф-ии- единицы компиляции. Но там она была хороша только тем, что она там есть. Она никак не была поддержана средствами защиты.
-->РТ - одно из самых критичных свойств яп, которые ориентированны на индустриальное программирование.
Рассмотрим понятия:
Контекст трансляции(КТ) - совокупность имен и их атрибутов, необходимых для трансляции модуля:
- В контекст входят имена, которые не определяются в самом модуле, но используются в нём;
- Всегда не пуст;
Даже при цельной трансляции существуют имена, которые не определены:
для стандартного Паскаля в контекст входят стандартные идентификаторы (их смысл определяется транслятором, но они не входят в стандартные служебные слова)--> в КТ определяют имена стандартного окружения и только они.
Раздельная трансляция:
Из единиц компиляции (ЕК) есть понятие экспорта и всё, что экспортируется, образует контекст трансляции (КТ),
было реализовано уже в Assembler--> C
Очередная ЕК подаётся на вход транслятору, который затем формирует объектный файл. Это присуще любой системе с раздельной трансляцией. Различия в том, что возникает два понятия - трансляционная библиотека(ТБ)
(контекст трансляции- предназначена для транслятора) и программная библиотека(ПБ) (все модули на объектном языке, необходимые для того, чтобы собрать готовую программу- предназначена для редактора).Некоторая информация, находящаяся в программной библиотеке, не нужна в трансляционной библиотеке.
Пример:
статическая функция, которая необходима только в этом файле--> необходима в программной библиотеке, но информация о ней не попадает в контекст трансляции.
Простейший вариант раздельной трансляции:
ЕК поступает на вход транслятору, который формирует некоторую часть программной библиотеки (объектный модуль). Откуда в такой схеме берётся контекст трансляции?
Если транслятор видит только ЕК, то весь контекст трансляции должен быть в ЕК. В языке должны быть конструкции, описывающие этот контекст:
В ассемблере - это конструкции:
PUBLIC имя
EXTRN W: WORD - конструкция импорта, находится в другом файле.
В языке Си подобного рода объявления - это ключевое слово extern.
4.1. Языки с такой схемой - языки с раздельной независимой трансляции (компилятор в один момент видит только одну единицу компиляции).
Очевидной недостаток таких языков - каждая ЕК должна заново описывать свой контекст. И если две ЕК используют один контекст, то появляется некоторая избыточность. Если мы перепутаем, укажем неверный спецификатор, то никто никакой ошибки не найдёт - язык объектных файлов не содержит такой возможности.
Максимум- предупреждение.
ЕК0
int a;
ЕК1
extern int a;
ЕК2
extern int a;
И на помощь приходит технология - если мы описываем какой-то контекст, экспортируем, то мы должны интерфейс (всю совокупность экспортированных имён) выносить в специальный header-файл.
Если нужно имя переменной, типа,..., то вместо объявления extern надо целиком включить контекст трансляции - нужный header-файл при помощи #include.
Если надо включить в программу M2.h и M3.h, а М2 содержит в себе объявления из М3, то с точки зрения компилятора Си при объявлении нескольких extern-переменных ничего страшного не возникнет. Но беда может возникнуть если мы объявляем типы:
например, если в М3 содержится описание структуры:
struct c {
}
--> двойное объявление
Выход - проверка на объявление специального символа, например для модуля MyModule.h это может быть __MYMODULE_H__(уникальный для данной единицы трансляции)
И проверка будет иметь вид:
#ifndef __MYMODULE_H__// определен модуль или нет?
#define __MYMODULE_H__// описание, если не было ранее.
...сам модуль MyModule.h....
#endif
И при такой технике гарантируется, что каждое имя описано только один раз.
НО(для С и С++):
1. При изменении одного header-модуля нужно перетранслировать все модули, которые его включают.
2. У нас только единое пространство имён.
Все header-файлы образуют трансляционную библиотеку.
#include <stdio.h>
#include <io.h>
#include <net\inet.h>// необходимо подключить еще библиотеку
lseek()-достаточно include
Но для использования gethostname() компилятору уже надо подключать специальную библиотеку при помощи следующих команд линковщика:
-l inet
-L build_path // путь к этой библиотеке
причем в разных средах- будет воспринято по-разному, к тому же путь к библиотеке не будет одинаковым.
Язык С++ использует независимую трансляцию, чтобы облегчить распространение этого языка.
4.2. Раздельная зависимая трансляция.
Транслятор когда читает ЕК, то он черпает информацию из нее и из ТБ (трансляционной библиотеки) - и на выходе будет не только ПБ, но и обновление трансляционной библиотеки. Директивы описания контекста ориентированы на имена модулей.
Языки 70-х, 80-х, 90-х гг.
В этой трансляции физический модуль (ФМ) аналогичен логическому модулю (ЛМ).
Такое наблюдается, например, в языке Дельфи.
Юнит(ЕК) в Дельфи транслируется в DFU, который содержит объектные модули и таблицу символов(ПБ и ТБ).
В Модуле-2 есть понятие библиотечного модуля, который делится на definition mudle и implementation module. На уровне исходного текста разделены ПБ и ТБ.
Та же самая идея и в Обероне - у нас существует единая ЕК - модуль. Реально оттранслированный код модулей выглядит как DFU - оттранслированный текст начинается с таблицы символов(все, что помечено *), а далее объектные модули.
Ключевые слова в этих языках:
IMPORT модуль -список имен модулей (компилятор подгружает их)
USES модуль- для delphi;
Но здесь структура всё равно очень проста за счёт того, что ЛМ совпадает с ФМ.
Наиболее мощно это реализовано в языке Ада.
Восходящий подход (bottom-up) - сначала компилируются модули нижнего уровня, потом выше(модули, основанные на предыдущих) и т.д. Сервисные модули ничего не знают о своих клиентах - типичный случай одностороннего связывания.
Модули, которые экспортируют ничего не знают о модулях, которые их импортируют.
М1 импортирует М2, а М2 импортирует М1 (зацикливание) - разрешение такой ситуации невозможно.
Си и Модула-2 находят эту ошибку на этапе трансляции(препроцессор):
Си : Будет попытка открыть файл на чтение, который уже открыт на запись.
Модула-2: М2 откомпилирован? он может быть откомпилирован только после компиляции М1, который сейчас компилируется.
Решение проблемы - переделка одного или нескольких модулей.
Ключевое отличие языка Ада - он поддерживает и восходящее, и нисходящее программирования(вложенные пакеты).
Там есть понятие пакетов.
Внутри какого-то пакета М могут быть вложенные пакеты М_inner, M1_inner:
package M is
package M_inner is
package M1_inner is
end M1_inner;
end M_inner;
end M;
Соответствующие тела вложены:
pakage body M
body M_inner
body M1_inner
end M;
если мы можем отдельно транслировать пакеты, то мы должны транслировать и вложенные пакеты.
Ситуация со вложенностью отличается зависимостью пакетов, М от М1, т.к. он может использовать имена М1, и М1 зависит от М --> В результате, у нас возникают двусторонние связи.
И Ада поддерживает и односторонние связи.
Основной принцип языка Ада - раздельно транслируемая программа не должна отличаться от цельно транслируемой - чтобы можно было программу транслировать единым куском, а не несколькими единицами компиляции.
можно программу разбивать на:
-
совокупность одноуровневых единиц компиляции( модула-2, оберон, delphi);
-
тела пакетов;
У нас возникает понятие первичных модулей и вторичных модулей.
Первичные модули(спецификация пакета) - это библиотечные модули в таких языках как Модула-2.
Тело пакета - вторичный модуль. Вторичный модуль - раздельно транслируемая процедура или функция.
В первом способе, когда мы разделяем программу на совокупность частей, у нас появляется специальное описание:
WITH список пакетов- подгружает контекст трансляции.
текст ЕК
Здесь единицей контекста является только пакет.
Это один в один соответствует конструкциям import и uses в Обероне и Дельфи соответственно.
WITH M1 is
package M2 is
X:T; // ERROR, т.к. Т видно только потенциально.
X:M1.T; // OK - видимость только через имя соответствующего модуля
Но есть модификация:
WITH список_пакетов;
USE список_пакетов2; // этот список - подмножество первого списка
Это напоминает конструкцию USES из Дельфи - там она подгружает список имен из соответствующего юнита и делает их непосредственно видимыми .
Это аналог конструкции одностороннего связывания модулей типа языков Оберон и Дельфи.
Но поддерживается ещё и двустороннее связывание - можно раздельно транслировать тело и спецификацию пакета М, но и тело пакета M_inner.
для with, нет обратной конструкции, т.к. это одностороннее связывание.
Для этого нам нужна пара конструкций:
Вместо спецификации пакета M_inner, мы пишем:
package M_inner is separate;- говорит компилятору поискать таблицу имен M_inner где-то еще.
Это в одну сторону, а в другую сторону -
package M is stub
...
package M_inner is separate // stub - заглушка, при трансляции увидев заглушку, компилятор полезет в ТБ и загрузить таблицу имен для М_inner, не зная таблицы имен транслировать дальше нельзя
...
end M;
Что будет, если мы хотим оттранслировать отдельно тело пакета M_inner:
package body M_inner is
end M_inner
нельзя, т.к. М_inner внутри пакета М
separate (M) package body M_inner is
...
end M_inner;-двустороннее связывание
(М)-имя объемлющего пакета.
Когда компилятор транслирует, какую таблицу имён он должен подключить в этой точке (точке, где стоит (М))? Рассмотрим 2 возможности - эту и ещё:
WITH M;
package MM is
...
end MM; - это одностороннее связывание, надо подключить все имена из спецификации пакета М.
Для двустороннего связывания где-то должна стоять заглушка (в теле пакета М):
package body M_inner is separate
К моменту точки, где (М) должны быть оттранслированы все имена пакета М , совокупность имён из начала пакета М (до заглушки).
Mы вынесли М_inner и физически оно находится вне пакета М(транслируется как отдельная единица), но мы хотим, чтобы сохранялась вложенность этого пакета в М(доступ к именам). Т.о. тело пакета М должно быть оттранслировано раньше.
Это существенно усложняет процесс трансляции.
Сохранить всю таблицу имён - сохранить то, что было объявлено в М до заглушки M_inner.
Заглушки говорят, что в этой точке надо сохранить всю таблицу имен, потом при трансляции в этой точке она понадобится.
Двустороннее связывание есть только в языке Ада при помощи существенного усложнения механизма раздельной трансляции. Еще одной проблемой ,усложняющей структуру,является раздельная трансляция родовых пакетов.