ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 6 Г Л А В А 7 ============ КОМПИЛЯТОР ========== Компилятор JPI Мodula-2 имеет одно-проходимую структуру, это значит, что он выдает сгенерированный код одновременно с чтением исходного текста; временные файлы не используют- ся. Импортирование выполняется рекомпиляцией файлов модулей определений (DEF-файлов), если потребуется. Это устраняет необходимость специальных файлов "Таблицы идентификаторов" и оз- начает, что определения модулей не нуждаются в явной компиляции; они вызывают последующую компиляцию, как только будут модифицированы. Это также устраняет ограничения порядка компи- ляции, которые обычно соответствуют определениям модулей; реализации модулей могут быть от- компилированы в любом порядке. Выходом является переместимый обьектный файл (OBJ-файл) в формате Intel/Microsoft. Группа таких файлов собирается в один выполняемый файл (EXE-файл) линкером. OBJ-ФАЙЛЫ ========= OBJ-файл содержит специальную информацию, которая упрощает компоновку, проверяет неп- ротиворечивость (целостность) и минимизирует размер EXE-файла. Записи включения - это вставки, которые сообщают компоновщику имена файлов, используе- мых модулем. Это означает, что только имя главного модуля необходимо определить для компо- новки. Записи версии - это также вставки, которые записывают дату/время используемых DEF-фай- лов. Компоновщик проверит, все ли модули соответствуют этим версиям; если нет, появляется возможность фатальной противоречивости, которая может быть устранена перекомпиляцией (см. опцию Make ниже). OBJ-файл построен как библиотеки, что означает, что модуль разбит на секции, которые включаются компоновщиком, если используются. Это устраняет потребность в больших модулях из универсальной библиотеки. Каждая глобальная процедура - это секция сама по себе, так же и группы обьявленных данных, принадлежащие к соответствующим частям VAR или CONST. ПРЕДСТАВЛЕНИЕ ДАННЫХ ==================== Базисная единица памяти - 8-битовый байт. Каждый обьект Мodula-2 представлен числом байтов, определенным его типом. Типы подмножества представляются, как их базовые типы. Взятие ADDRESS обьекта дает сброс первого байта его в памяти. Для многобайтовых число- вых типов последний значащий байт хранится в младшем адресе. Типы CARDINAL представлены двоичными числами без знака: SHORTCARD : 1 байт CARDINAL : 2 байта LONGCARD : 4 байта Целые типы представлены как двоичные числа с 2-мя дополнениями: SHORTINT : 1 байт INTEGER : 2 байта LONGINT : 4 байта Тип REAL представлен как пары мантисса/экспонента для процессора 8087 (см. док., отно- сящуюся к 8087). REAL : 4 байта LONGREAL : 8 байтов Перечислимые типы представлены, как двоичное число без знака (перечисление) <= 256 значений : 1 байт >= 256 значений : 2 байта BOOLEAN : 1 байт CHAR : 1 байт Тип "абсолютный указатель" занимает 4 байта, где первые 2 содержат значение смещения, а последние 2 - значения сегмента. NIL представлен как (0,0). Базовый указатель занимает 2 байта, содержащих сегмент. Процедурные переменные FAR представлены, как абсолютные указате- ли на первый оператор кода процедуры. Процедурные переменные NEAR представлены как 16- би- товые смещения в пределах подходящего сегмента кода. Множества представлены упакованной битовой картой с одним битом для каждого потенци- ального члена элементарного типа, как указано [0..N]. Число требуемых байтов равно 1 + (N DIV 8). Элемент E (0 <= E <= N) представлен битом номер Е MOD 8 в байте номер E DIV 8. Элементы массивов хранятся в последовательных ячейках памяти и располагаются по воз- растанию индексов. Общий размер - это размер элемента, умноженный на число элементов. Поля записей также хранятся в последовательных ячейках и в порядке, в котором они опи- саны в определении типа; каждое поле хранится согласно своему типу. Общий размер - это сум- ма размеров полей. Вариантные части имеют особенность: поля различных альтернатив накладываются (совмест- ная память) и общий размер вариантной части есть сумма размеров полей большей альтернативы. Пример: TYPE R = RECORD F1: INTEGER; (* Байты 0-1 *) CASE F2: BOOLEAN IS (* Байт 2 *) | FALSE: F3, (* Байт 3 *) F4, (* Байт 4 *) F5: CHAR; (* Байт 5 *) | TRUE: F6: CARDINAL (* Байты 3-4 *) END; F7: SET OF [0..20]; (* Байты 6-8 *) END; (* SIZE (R) = 9, VSIZE(R.F6) = 5 *) СОГЛАШЕНИЯ О ВЫЗОВАХ ==================== Эта часть требует ознакомления с архитектурой 8086-процессора; мы будем обращаться к регистрам по именам: AX(,AL), BX, CX, DX, SI, DI, BP, SP, CS,SS,DE,ES. Область стека ------------- Процедура активизации использует аппаратный стек (определенный SS или SP) для несколь- ких целей: - Передачи параметров. - Сохранения адреса возврата. - Сохранения регистров, которые используются и должны быть сохранены. - Сохранения содержимого 8087-сопроцессора, если недостаточно стекового пространства 8087, и оно оставлено для вспомогательных вычислений с плавающей точкой. - Построения таблицы внешних контекстов для доступа к переменным, локальным по отноше- нию к близлежащим процедурам. - Память для локальных переменных (созданных пользователем либо компилятором). - Копии параметров массива открытых значений с тем, чтобы они могли быть модифицирова- ны без модификации фактических параметров. - Сохранения приоритетов вызывающих процессов. Параметры поставляются в стек вызывающими процессами; остаток строится вызванной про- цедурой на входе. Регистр BP указывает на базу стека активизации и используется для доступа к локальным переменным и параметрам. Общий рисунок имеет вид: старый SP ->+--------------------+Высшие адреса :параметры :Действующие параметры +--------------------+ :возврат - CS :Возвратный сегмент для : :FAR :возврат - IP :Возврат смещения новый BP ->+--------------------+ :сохраненный BP :BP вызывающей программы :таблица BP :Регистры BP объемлющих : :процедур :локальные переменные:Локальные переменные : :пользователя :локальные временные :Локальные анонимные : :переменные :сохраненные регистры:Регистры вызывающей : :программы :копии значений :Копии массивов парамет- : :ров :приоритеты :Сохраненные приоритеты : :(процессов) :сохраненный 8087 :Содержимое 8087 процес- : :сора вызывающей про- : :граммы новый SP ->+--------------------+Нижние адреса Только требуемые части строятся в действительности; для очень простых процедур доста- точно сохранения регистра BP. Завершаясь, процедура восстановит стек и сохраненные регистры (см. директиву $C) в состояние, в котором они находились до вызова; это включает освобождение памяти, используе- мой параметрами. Передача параметров ------------------- Параметры выводятся в стек в том порядке, в котором они появляются в объявлении проце- дуры. Конкретно, что выводится - зависит от типа соответствующего формального параметра и является ли он переменной или нет. Все параметры-переменные и любые открытые массивы пере- даются указателем: передается полный адрес в формате сегмент-смещения, и процедура исполь- зует его для доступа в памяти к фактическому параметру. Такой параметр занимает 4 байта вне зависимости от типа. Переменные параметры, исключая открытые массивы, передаются помещением фактического значения параметра в стек. Занимаемая память задается типом параметра, исключая тот факт, что 1-байтовый тип занимает 2 байта в стеке. Для значения параметров открытых массивов процедура (необязательно, см. директиву $V) будет делать копию передаваемого значения в локальной памяти и модифицировать адрес, пере- данный в точку, в копии. Для параметров открытых массивов (переменных или нет), вызывающая процедура будет по- мещать (перед адресом) размер (2-байтовый) в байтах фактического массива. Результаты функций ------------------ Результаты из функций возвращаются разными способами, зависящими от их типа. Типы REAL и LONGREAL возвращаются на вершине стека процессора 8087. Другие скалярные, небольшие множества, указатели и процедуры-переменные возвращаются в регистрах 8086-процессора в зависимости от их размера: 1 байт в AL, 2 байта в AX и 4 байта в DX:AX. Множества других размеров, массивы и записи возвращаются в стеке под параметрами: пе- ред помещением параметров вызывающая процедура распределяет требуемую память. ОПЦИИ (В КОМАНДНОЙ СТРОКЕ) ========================== Вызывая компилятор, опции определяют, что требуется от него. Они (опции) определяются после имени файла при вызове компилятора из командной строки MS-DOS или из меню других опе- рационных сред. Например: M2 /C MyMain /ML Опции: /F - Разрешает, чтобы имена файлов отличались от имен модулей. /Н - Использованная совместно с Make (см. ниже) покажет, какие файлы нужно перекомпи- лировать. /J - Производит небиблиотечные OBJ - файлы, подразумевая, что все они будут включены в процесс компоновки. Это необходимо для некоторых компоновщиков. /L - Производит протокол, содержащий информацию о скорости компиляции. /М - Вызывает утилиту Маке. Файл, переданный компилятору, должен содержать главный мо- дуль и Маке, основываясь на импорте, будет искать все модули, требуемые программой. Для каждого из них проверяется, является ли соответствующий OBJ - файл новым, на основании вре- мени и даты, когда файл использовался. Те, которые необходимо перекомпилировать - переком- пилируются. Учитываются только те файлы, чьи модули реализации доступны. /N - Включает строки-номера в OBJ - файл. Это позволяет, например, отладчику знать со- ответствие между адресами кода и строками исходной программы. /D - Запрещает реорганизацию выражений. Это гарантирует подсчет выражений слева напра- во, но результат - в неоптимальном коде. /Р - Во время компиляции высвечивает имя текущей процедуры для обозначения течения процесса. /R - Использованная вместе с Маке, будет рекомпилировать все, вне зависимости от про- верки. Может быть необходима, поскольку Маке только сравнивает фактическое время/дату фай- лов; он не читает OBJ - файлы для поиска записи о версии. /V - Делает все переменные изменчивыми: вместо того, чтобы пытаться сохранить перемен- ные в регистрах, сохраняет их в памяти. Это может облегчить отладку. ДИРЕКТИВЫ (В ИСХОДНОМ ТЕКСТЕ) ============================= Директивы, появляющиеся в исходном тексте, используются для определения различных де- талей, касающихся того, как должен производиться код. Они определены в специальных коммен- тариях, где первый символ - $. Каждая директива обозначена одиночной буквой, за которой обычно следует параметр; несколько директив могут быть даны в одном комментарии, разделен- ные запятыми. Большинство директив - флажки: параметр "+" или "-" отмечает, доступен признак или нет. Они могут быть сброшены до предыдущего значения (перед новой директивой) посредством "=". Пример: (*$V-,M mycod,N*) .... (*$V=,F*) Директивы действительны от места, где они впервые встречаются, до конца исходного текста или пока не отменится другой директивой текущее. Некоторые директивы, определенные в модуле определений, отменяют любые назначения в модуле реализации. Это C, G, F, K, N. Директивы: $A+/- Разрешает (по умолчанию)/запрещает алиасы для глобальных переменных. Компилятор будет предполагать, что переменные, объявленные с заблокированной директи- вой, не могут быть доступны прямо и косвенно в одно и то же время. $B+/- Включает (по умолчанию)/исключает обработку <УПРСТОП> (Ctrl-Break) в программе. При включении ее нажатие клавиши <УПР-СТОП> прервет программу; может динамически включать ся/отключаться библиотечными процедурами. Эта директива должна быть в головном модуле. $C h Выбирает, какие регистры будут сохранены процедурами. Шестнадцатеричный номер h определяет регистры: AX=1, CX=2, DX=4, BX=8, DS=10, ES=20, SI=40, DI=80. По умолчанию F0= DS+ES+SI+DI. BP всегда сохраняется. $D n Определяет имя n сегмента (данных), в который должны помещаться глобальные пере- менные, объявленные в модуле. По умолчанию используется имя модуля. В любом случае перед именем должно быть D_. Эта директива должна появиться перед ключевым словом MODULE как в описательной, так и в исполнительной частях. $E+/- Включение/выключение (по умолчанию) упрощенного обращения к вариантам записи по алиасным именам. Вследствие перекрывания компилятор будет нормально считать изменения полей вариантных записей, вызывая их в памяти. $F Процедуры будут вызываться посредством FAR-вызовов. Также процедурные типы имеют 32 бит. Это по умолчанию и относится только к глобальным процедурам. Локальные - всегда NEAR. $G+/- Включает (по умолчанию)/выключает префиксы модулей во внешних именах. Включение гарантирует, что внешние имена уникальны. $H+/- Включает/выключает (по умолчанию) трактование составных констант как переменных, разрешая их модифицирование. Это возможно, поскольку они находятся в памяти. $I+/- Включает/выключает (по умолчанию) проверку индекса. В случае включения, обраще- ние к несуществующему элементу массива вызовет ошибку выполнения. Предполагается, что все переменные имеют разрешенные значения в соответствии с их типом (см. $R ниже). Также пред- полагается, что преобразование типов индексов не вызовет проблем. $J+/- Включение/выключение (по умолчанию) процедур прерывания посредством выработки возврата IRET вместо обычной команды RET. $K+/- Включение/выключение (по умолчанию) соглашения по вызовам процедур для языка "Си". Это определяет, что вызывающая (не вызываемая) процедура убирает параметры и устанав- ливает DS-регистр на группу, называемую "DGROUP". $M n Определяет имя (n) (кодового) сегмента, в который поместить код. По умолчанию ис- пользуется имя модуля. В любом случае имени предшествует С_. Эта директива должна появиться до ключевого слова MODULE как в файле определения, так и в исполнительном. $N Процедуры будут вызываться вызовами NEAR. Это требует, чтобы вызывающие процедуры находились в том же сегменте. Также тип процедуры должен быть 16-битовым. $O+/- Включает/выключает (по умолчанию) проверку переполнения по всем числовым опера- циям. При включении числовое переполнение вызовет ошибку выполнения. $P+/- Включает/выключает (по умолчанию) создание внешних имен для локальных процедур. Включение упрощает отладку, но может вызвать конфликт имен. $Q+/- Включает/выключает (по умолчанию) трассировку процедур. При включении процедуры будут выполняться с командой прерывания 60Н на входе и 61Н на выходе. Обработчики этих пре- рываний должны быть полностью установлены. $R+/- Включает/выключает (по умолчанию) проверку подмножеств. При включении ошибка вы- полнения вырабатывается посредством присвоения и передачи параметров, если значение выходит за пределы границы типа получателя. $S h Определяет размер стека в 16-ричном числе "h" для программы. Эта директива должна быть в головном модуле. $S+/- Включает/выключает (по умолчанию) проверку переполнения стека. При включении вы- рабатывается ошибка выполнения в случае, если исчерпан объем стека. $V+/- Включает (по умолчанию)/выключает копирование параметров значений открытого мас- сива. Выключение увеличивает эффективность, но потенциально неверно. $W+/- Включает/выключает (по умолчанию) изменчивость переменных. Изменчивые переменные не содержатся в регистрах операторов. Это может быть существенно, если конкурирующие про- цессы взаимодействуют посредством разделенных глобальных переменных. Опция командной строки /V дублирует эту директиву полностью. $X+/- Включает/выключает (по умолчанию) слежение за процедурами со стороны процессора 8087. Это необходимо, если вызывающие процедуры вложенных функций исчерпывают стек 8087. $Y+/- Включение/выключение (по умолчанию) совпадающих вариантных полей. В случае вклю- чения разрешается использовать одинаковые имена полей в различных вариантных альтернативах при условии, что они имеют одинаковый тип и находятся на одинаковом смещении в записи. $Z+/- Включение/выключение (по умолчанию) проверки для разыменовывания указателей NIL, которые в этом случае вырабатывают ошибку исполнения. Когда директива включена, все локаль- ные переменные инициализируются в "0". Если включение осуществлено в головном модуле, все глобальные переменные обнуляются. ИНТЕРФЕЙС С ДРУГИМИ ЯЗЫКАМИ =========================== Поскольку компилятор производит стандартные OBJ-файлы, возможно компоновать их с ко- дом, написанном на других языках и откомпилированным другими компиляторами. Все, что вызывается из Modula-2, должно быть объявлено в терминах Modula-2, так что определения модулей (OBJ-файлы) должны быть написаны даже для частей, которые не исполняют- ся на языке Modula-2. Но настоящий OBJ-файл вырабатывается другим компилятором. Чтобы это работало, различные части должны согласовываться по структуре выполнения всей программы. Поэтому надо знать структуру выполнения как Modula-2, так и других исполь- зуемых языков. Просмотр MAP-файлов, созданных компоновщиком, оказывает большую помощь при сборке фрагментов вместе и просто для понимания. Структура выполнения Modula-2 может быть скроена директивами: $D - выбор сегмента, куда пойдут данные; $M - выбор сегмента, куда пойдет код; $G - выключает префикс имени модуля; $N - производит процедуры NEAR; $F - производит процедуры FAR; $C - выбирает, какие регистры сохраняются процедурами; $K - следует соглашениям о вызовах языка "Си". Сегменты, группы и классы ------------------------- Концепция сегмента - фундаментальна для процессора 8086, и она усовершенствована кон- цепциями группы и класса в OBJ-языке. Сегмент - это совокупность элементов, чей общий размер меньше, чем 64К. Имеет имя. Лю- бой элемент принадлежит одному сегменту. Группа - совокупность сегментов, чем общий размер также меньше 64К. Также имеет имя. Любой сегмент принадлежит одной группе или не принадлежит группе. Во время компоновки множества OBJ-файлов все элементы должны быть упорядочены в физи- ческой памяти так, чтобы обеспечить наличие элементов одного сегмента или группы в пределах одной области в 64К. Сегменты уточняются посредством концепции поименованных классов, которые имеют приори- тет при упорядочении элементов; элементы с одинаковым именем класса располагаются по со- седству, в пределах классов элементы из одного сегмента располагаются по соседству. Классы имеют приоритет, так что если 2 элемента имеют одно имя сегмента, но разные имена классов, они даже не считаются принадлежащими одному сегменту в 64К. Компоновщик согласует классы в порядке, в котором они встречаются во время компоновки. Компилятор использует это для достижения правильного расположения памяти в конечной прог- рамме: сначала идет код (классы CODE, FCODE), затем глобальные данные (M_DATA), затем сте- ковые данные (STACK) и, наконец, динамические данные (HEAP). Компилятор для каждого модуля произведет 4 сегмента и одну группу с именами, получен- ными из имени модуля: C_module - содержит код для процедур; K_module - содержит структурные константы; S_module - содержит специальные сегментные константы; D_module - содержит глобальные переменные; G_module - группа, содержащая С_, К_ и S_сегменты. Сегменты могут быть переименованы директивами компилятора: $D обращаюся к D_сегменту, $M к C_, K_, S_сегментам и к G_группе. Это позволяет заставить различные модули использо- вать одинаковые сегменты: например, используя (*$D DATA *) во всех модулях можно поместить все глобальные переменные в один 64К- байтовый сегмент. Следующий сегмент INITCODE содержит инициализационный код для модулей и код для голов- ного модуля. Если (* $M CODE*) определена, INITCODE и C_CODE помещены в группу, называемую G_CODE, позволяя всем кодам находиться в пределах одной группы в 64К. Поскольку имеется одиночный сегмент данных и группа кодов для каждого модуля, их инди- видуальный код и глобальные данные ограничены 64К байтами каждый. Полная группа может иметь произвольно большой код и данные. Адресация элементов ------------------- Физический адрес состоит из двух частей: адрес сегмента и смещение. Все элементы в од- ной группе или сегменте имеют один и тот же адрес сегмента, но разные смещения. Чтобы адресовать элемент, сегментный регистр должен содержать его адрес сегмента. Этот (неявный) регистр объединяется с явным значением смещения, чтобы адресовать элемент. Вызовы FAR, с другой стороны, определяют как адрес сегмента, так и смещения явно. Таким образом, отдельные объекты могут быть доступны без перезагрузки сегментного ре- гистра, только если они принадлежат одному сегменту или группе. Аналогично, процедура может быть вызвана только вызовом NEAR, если вызывающая процедура находится в пределах того же кодового сегмента (так, чтобы CS-регистр не нужно было бы изменять). Является ли процедура NEAR или FAR (директивы $N и $F), отображается ее командой RETurn (возврат) - она не должна ничего делать с сегментом, в котором находится, или с чем- нибудь еще в нем. Соглашения об именах -------------------- Различные OBJ-файлы обращаются к элементам друг друга посредством экспортируемых внеш- них имен. Онм могут быть уникальны и определенны. Компилятор формирует внешнее имя элемента, используя в качестве префикса перед именем исходного кода имя модуля, в котором он объявлен. Символы $ или @ отделяют эти два имени; $ для FAR-процедур, @ для NEAR-процедур и данных. Вставку префикса можно отключить директивой $G. Элементы, объявленные в локальных модулях непосредственно внутри глобальных модулей, получают префиксами имена как глобальных, так и локальных модулей, разделенных @. Пример: (*$D XYZ*) DEFINITION MODULE M: (* Сегменты C_M, K_M, S_M, D_XYZ *) V: INTEGER; (* Внешнее имя M@V (сегмент D_M) *) PROCEDURE F; (* Внешнее имя M$F (сегмент С_M) *) (*$N,G-*) PROCEDURE N1; (* Внешнее имя N1 (сегмент C_M) *) (*$G+*) PROCEDURE N2; (* Внешнее имя M@N2 (сегмент С_М) *) (*$F*) MODULE L; PROCEDURE LP; (* Внешнее имя M@L$LP (сегмент С_М) *) ..... END L; END M. ПОДДЕРЖКА СОПРОЦЕССОРА 8087 =========================== Компилятор вырабатывает команды для сопроцессора 8087 для операций с плавающей точкой. Во время выполнения они будут выполняться самим 80x87-чипом или будут эмулированы программ- но. Эмулятор включается, если любая часть программы использует команды с плавающей точкой. Во время выполнения эмулятор проверяет наличие чипа 80х87 и, если да, перепоправляет вызы- вающие эмулятор процедуры к 8087-командам. Такой же EXEфайл может, таким образом, выпол- няться на любой машине с максимально возможной скоростью. Если известно о наличии 80х87-чипа, для экономии памяти во время компоновки может быть предусмотрен пустой эмулятор. Это сделано путем замещения файла SYSTEM.OBJ OBJ-файлом, ко- торый определяет необходимые общие внешние имена в "0"; также должен быть использован дру- гой MATHLIB.OBJ. Два таких файла поставляются с расширением .7BJ. Обычно особые ситуации для 8087 запрещены. Программы-обработчики могут быть инсталли- рованы (установлены) посредством импортирования модуля FloatExc (см. ГЛАВУ 8). Обработчики прерываний ---------------------- Возможно написать обработчики прерываний на Modula-2, хотя не следует пытаться делать это без полного понимания процессора 8086 и MS-DOS. Обработчик прерывания пишется, как процедура Modula-2; директива $J+ даст ей соответс- твующий оператор возврата и директива $C FF заставит сохранить все регистры. Директива $ W+ может быть использована для сохранения в памяти общих глобальных переменных. Аппаратные прерывания могут быть включены/выключены процедурами EI и DI из модуля SYSTEM. Обработчик устанавливается путем назначения его адреса одному из векторов прерываний в младших разрядах памяти. Пример: MODULE Int; VAR PrtInt [0:5*4]: ADDRESS; (* Прерывание 5: печать экрана. *) OldInt: ADDRESS; (*$W+*) Flag: BOOLEAN; (*$W-*) (*$J+,C FF*) PROCEDURE Handler; BEDIN ... END Handler; (*$J-,C F0*) BEGIN OldInt := PrtInt; PrtInt := ADR(Handler); ... PrtInt := OldInt; END Int. Самые предприимчивые смогут также писать программы с возможностью прекратиться и ос- таться резидентом, используя вызов MS-DOS 031H. Обработка прерываний должна быть отключена ($B-) и стек может быть малым, например, $S 500. Наконец, требования к динамической области ЕХЕ-файла должны будут удовлетворены с использованием утилит SETHEAPS; в противном случае не останется памяти для других программ. Г Л А В А 8 ============ БИБЛИОТЕКА JPI Modula-2 ======================= ВВЕДЕНИЕ ======== Эта глава описывает библиотечные модули, поставляемые с компилятором JPI Modula-2. JPI Modula-2 поставляется с 12-ю различными модулями, все вместе содержат 250 процедур. Большинство из этих модулей - это обычные модули, написанные на языке Modula-2. Все они имеют описательную часть Modula-2, но некоторые из исполнительных написаны на языке Ас- семблера. Следующие части этой главы описывают каждый модуль в деталях, но вначале представлен обзор модулей. Описано, также, как обращаться из модуля к модулю. Модули могут быть разделены последовательно по более абстрактным функциям, где каждый уровень использует более низкие уровни: Уровень Модуль System : SYSTEM Assembly : AsmLib, MATHLIB Utility : Str,Lib,Storage,Process,Graph,FloatExc IO : IO, FIO Window : Window |