Add full compiler toolchain, libc, examples and reference docs

First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.

What's in here:
  bin/sprinter-cc        — driver script invoking SDCC + linker + mkexe
  libc/                  — Sprinter-specific libc layer over ESTEX/BIOS
                           (conio, gfx, io, mem, stdio + headers)
  runtime/               — crt0 variants (default/small/banked/minimal)
                           + heap + bank trampolines
  toolchain/             — mkexe (SprintEXE packer, C + tests)
  examples/              — 30 demo programs (gfx, file I/O, env, time, …)
  lib/Makefile           — builds the libc archive (sprinter.lib)
  docs/                  — converted Sprinter manuals + asm reference samples
  third_party/           — solid-c reference compiler dump + sdcc setup script
  release_docs/          — packaging / release notes

gitignore overhaul:
  • Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
    and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
    on macOS APFS).  Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
  • Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
    INPUT fixtures, not build outputs.
  • Collapse the duplicated SDCC/C/Sdcc sections into one section per
    concern (build outputs / vendored / OS-junk).
  • Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:13:21 +03:00
parent f542608b3f
commit c71e249a4e
404 changed files with 75155 additions and 58 deletions
+22
View File
@@ -0,0 +1,22 @@
#include <stdio.h>
char buffer[4]="AAA";
//char buffer[4]={'A','A','A','A'};
void main()
{
printf("0x%02X\n", *buffer); // (1) не правильно!
printf("0x%02X\n", (char *) *buffer); // (2) правильно
}
Если массив buffer представляет собой char-элементы, то при обращении к нему,
как показано в строке (1), компилятор сгенерирует не тот код, что нам нужно.
При этом, никакой ошибки выдано не будет. Для того, чтобы компилятор сгенери-
ровал правильный код, к отдельному элементу char-массива нужно обращаться так,
как это сделано в строке (2), принудительно указав компилятору через cast-
операцию то, что нам нужно.
При обращении к массиву, состоящему из int-элементов, вышеописанных проблем
не возникает, компилятор генерирует правильный код.
Для более наглядного представления, о чем идет речь, можно просмотреть asm-
листинг, генерируемый компилятором для приведенного выше примера. Для строки
(1) компилятор не сгенерирует код "ld b,0".
+968
View File
@@ -0,0 +1,968 @@
Компилятор языка Си в системе ESTEX
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ВВЕДЕНИЕ
Одной из целей разработки оптимизирующего компилятора SOLID C было создание
инструментального средства, которое позволяет писать и саму операционную сис-
тему, и программное обеспечение для нее. Для достижения этой цели в кодогене-
ратор введены различные дополнительные средства. Так, разработаны средства
автоматического распределения регистров, благодаря которым удалось существенно
повысить эффективность объектного кода. В результате стало возможным писать
на высоко-уровневом языке (которым является Си) даже системы, работающие в
условиях жестких ограничений по памяти и реальному масштабу времени. При этом
почти полностью устранена необходимость программирования на Ассемблере.
Основная цель данного руководства - дать пользователям представление о
применении компилятора в рамках операционной системы ESTEX и некоторых
особенностях этого компилятора.
SOLID C является двухпроходным компилятором языка, преобразующим исходный
си-текст в программу на языке Ассемблера. Компилятор состоит из двух файлов
"cc1.exe" и "cc2.exe", выполняющих соответственно первый и второй проходы.
На первом проходе входной си-текст преобразуется в объектный код (т-код),
сохраняемый в файле с расширением ".tmc" (по-умолчанию). На первом проходе
производятся также диагностика ошибок и выдача предупреждающих сообщений.
На втором проходе полученный т-код преобразуется в ассемблерный листинг,
сохраняемый в файле с расширением ".asm" (по-умолчанию).
Следует отметить тот факт, что поскольку данный компилятор является оптимизи-
рующим, то время, затраченное на компиляцию си-программы возрастает от 2-х и
более раз, по сравнению с обычными (не оптимизирующими) си-компиляторами.
ГЕНЕРАТОР ОБЪЕКТНОГО КОДА
Этот компонент (cc1.exe) компилятора обеспечивает первый проход. Он произ-
водит разбор исходного си-текста, его контроль на допустимость синтаксиса
и семантики. В результате первого прохода появляется промежуточный файл с
объектным кодом, т.н. Т-код. Стандартным расширением имен файлов, содержащих
т-код, является ".tmc".
Команда запуска компилятора первого прохода имеет следующий формат:
CC1 [опция] имя_файла
Имя файла может содержать букву диска и путь, при этом максимальная длина
строки не может превышать 80 символов. Обычно для исходных си-файлов исполь-
зуется расширение ".c". Если расширение входного файла ".c", то его можно
не указывать.
Опции генератора объектного кода
-c В отличие от стандартного языка Си, компилятор SOLID C
обрабатывает гнездование (вложение) комментариев.
Данная опция отключает режим гнездования комментариев.
-dNAME Определить макро-определение с именем "NAME". Между буквой опции
и именем "NAME" пробелы не допускаются.
-e Режим переопределения выдачи сообщений в файл ошибок, имеющего
расширение ".err". Файл создается всегда по текущему пути исходного
".c" файла.
-jN Установка максимального числа ошибок компиляции. Происходит
прерывание компиляции, если число ошибок равно или превышает
"N". Между буквой опции и числом "N" пробелы не допускаются.
Например: -j30 (прерывание процесса компиляции при 30 ошибках).
-k Режим совместимости со стандартным языком Си (стандарт K&R).
Разрешает делать неявные объявления функций и параметров.
Данную опцию необходимо использовать при компиляции "чужых"
исходников.
-m Режим вывода на экран статистики по таблицам компилятора.
-oNAME Задать имя для выходного файла с т-кодом. Максимальная длина
строки полного пути (диск\каталог\имя) файла составляет
80 символов. Между буквой опции и именем "NAME" пробелы не
допускаются.
-rP:S:H Установка отношения размеров частей рабочей области компилятора:
P - пула, S - таблицы символов, H - хеш-таблицы.
По-умолчанию используется соотношение 13:6:4. В случае переполнения
какой-либо части, используйте эту опцию для назначения другого
соотношения частей рабочей области.
Между буквой опции и строкой "P:S:H" пробелы не допускаются.
-x Режим строгой совместимости с MSX-C компилятором. При этом
расширение файла т-кода меняется с ".tmc" на ".tco".
КОДОГЕНЕРАТОР
Кодогенератор (cc2.exe) выполняет второй проход компилятора. Он читает
файл с т-кодом и создает выходной файл на языке Ассемблера.
Команда запуска компилятора второго прохода имеет следующий формат:
CC2 [опция] имя_файла
Если у входного файла с т-кодом расширение не указано, то используется
стандартное расширение ".tmc". Если не указано расширение для выходного
файла - используется расширение ".asm". Максимальная длина строки
"диск\каталог\имя_файла" должна быть не более 64 символов.
Если путь расположения для выходного файла не задан, то по-умолчанию
он создается по текущему пути расположения входного файла т-кода.
Опции кодогенератора
-k Не удалять входной файл т-кода (.tmc) по завершению работы
кодогенератора.
-oNAME Задать имя для выходного asm-файла. Максимальная длина
строки полного пути (диск\каталог\имя) файла составляет
64 символа. Между буквой опции и именем "NAME" пробелы
не допускаются.
-rN Резервирование области под таблицу символов компилятора размером
N байтов (N - десятичный формат). Между буквой опции и числом "N"
пробелы не допускаются. Например: -r2000 (резервируется таблица
символов размером в 2000 байтов).
-uX Определить пост-символ "X" для имен в asm-листинге. По-умолчанию
используется символ "_". Например: -u@ (использовать пост-символ
"@" для имен в выходном asm-листинге).
-q "Тихий" режим. Индикаторы процесса компиляции "---" и "+++"
на экран не выводятся.
ОСОБЕННОСТИ КОМПИЛЯТОРА SOLID C ДЛЯ ОС ESTEX
Как было сказано выше, целью разработки данного Си-компилятора было созда-
ние продукта, с помощью которого можно было бы (пользуясь всеми преимуществами
языка высокого уровня) разрабатывать программы, удовлетворяющие жестким требо-
ваниям реального масштаба времени и ограничений на память. В данной главе опи-
саны особенности компилятора с этой точки зрения. Кроме того, здесь будут даны
рекомендации по программированию на SOLID C.
Компилятор SOLID C может работать в двух различных режимах. Один из них
является "собственным" для данного компилятора, а второй - служит для обеспе-
чения совместимости со стандартом языка Си. В этом режиме все арифметические
преобразования выполняются точно так же, как в стандартном языке Си. Самое
большое различие между этим режимом и "собственным" состоит в том, что в пос-
леднем не выполняются автоматические преобразования форматов при переходах от
типа "char" к типу "int". По-умолчанию, компилятор работает в "собственном"
режиме. Для смены режима работы компилятора служит опция "-k" на первом прохо-
де компилятора.
Собственный режим компилятора
Для данного режима характерны два новых формата: целой константы (intconst)
и символьной константы (charconst). Обычно указанные форматы используются иден-
тично с соответствующими форматами "int" и "char", однако в случаях анализа
типов операндов и выражений, и приведения их к соответствию, указанные два
формата рассматриваются как различные.
Как упоминалось выше, в этом режиме не производится автоматическое преобразо-
вание форматов при переходе от типа "char" к "int". Действуют следующие согла-
шения о преобразованиях типов:
unsigned + ? -> unsigned
int + ? -> int
char + ? -> char
intconst + ? -> intconst
charconst + ? -> charconst
Режим совместимости со стандартом языка Си
Выполняются все арифметические преобразования, действительные в рамках реа-
лизации стандартного языка Си. Единственное различие состоит в том, что компи-
лятор SOLID C интерпретирует тип "char", как значение без знака.
Для этого режима действуют следующие правила преобразования типов:
char -> int
unsigned + ? -> unsigned
int + int -> int
Рекурсивные функции
Как известно, все функции стандартного языка Си рекурсивны. В среде компи-
лятора SOLID C можно писать также и нерекурсивные функции. Для различия
функций этих двух типов введены и зарезервированы следующие ключевые слова:
"recursive" - рекурсивная и "nonrec" - нерекурсивная. Эти ключевые слова
записываются в заголовке функции перед ее именем и служат описателями режимов
ее выполнения. Это показано в следующем примере:
nonrec main()
{
printf("Hello, world!\n");
}
recursive
int factorial(n)
int n;
{
return(n > 1) ? n*factorial(n-1) : 1;
}
int max(a,b)
int a,b
{
return(a > b) ? a : b;
}
В приведенном примере даны определения: нерекурсивной функции main() и
рекурсивной функции factorial(). Для функции max() описатель режима выпол-
нения функции в явном виде не задан, поэтому она будет выполняться так, как
задано по-умолчанию. Обычно по-умолчанию задается режим выполнения функций
как рекурсивных. Однако это умолчание можно изменять с помощью следующих
предложений препроцессора:
#pragma nonrec
#pragma recursive
Первым предложением по умолчанию задается режим выполнения функций, как
нерекурсивных. Второе предложение предписывает рассматривать все функции,
как рекурсивные (если явно не оговорено противоположное).
Для случая, когда действует предложение #pragma, функция max() будет выпол-
няться согласно последнему описателю режимов "рекурсивная/нерекурсивная".
Рекомендуется давать явное указание "nonrec" кампилятору, если нужно, чтобы он
воспринимал функции как нерекурсивные. По такому указанию, компилятор гаран-
тированно сгенерирует объектный код, более короткий и более быстрый, чем в
других случаях. В обычных прикладных программах бОльшая часть функций нерекур-
сивна, поэтому в начале исходного текста следует писать предложения препроцес-
сора: #pragma nonrec
Функции с переменными параметрами
Компилятор SOLID C обрабатывает функции двух типов: с переменным числом
параметров и с фиксированным. В функциях первого типа число и представление
параметров могут изменяться, тогда как в функциях второго типа они фиксированы.
Например, функции printf, scanf и ряд других являются функциями с переменным
числом параметров. В компиляторе SOLID C функции указанных двух типов разли-
чаются с помощью специальных описателей.
Структура предложений языка изменяется с введением простых и абстрактных
описателей следующим образом:
С простым описателем:
идентификатор
(описатель)
*описатель
описатель()
описатель(.)
описатель [константное выражение]
С абстрактным описателем:
пусто
абстрактный описатель
*абстрактный описатель
абстрактный описатель()
абстрактный описатель(.)
абстрактный описатель [константное выражение]
Конструкции "описатель(.)" и "абстрактный описатель(.)" впервые введены
для обозначения функций с переменными параметрами. Конструкции "описатель()"
и "абстрактный описатель()" обозначают функции с фиксированными параметрами.
Данный метод описания функций с переменными параметрами используется в пред-
ложениях типа cast, sizeof. Например, в следующих описаниях функции func и fp
определены, как функции с переменными параметрами:
int func(.), (*fp)(.);
Перед применением функции с переменными параметрами, ее необходимо всякий раз
описывать подобным образом. В библиотеке стандартных функций имеются функции
с переменными параметрами, как например:
printf, fprintf, sprintf, scanf, fscanf, sscanf
Описания перечисленных функций содержатся в стандартном файле "stdio.h".
Вызов функции с переменными параметрами совершенно идентичен вызову функции
с фиксированными параметрами.
Эти функции не обладают мобильностью с точки зрения рабочей машины (главным
образом по методу доступа к параметрам). Отсутствие переносимости имеет место
только для самой функции с переменными параметрами. Внешняя функция, ее вызы-
вающая, является переносимой.
Определение функции с переменными параметрами должно быть записано в самом
начале, перед ее телом. Ниже приведено типичное определение функции func,
являющейся функцией с переменными параметрами:
int func(.); /* это определение обязательно должно идти в начале */
int func(nargs, args)
int nargs, args;
{
...
}
В момент вызова функции с переменными параметрами, вместо фактического
параметра подставляется его копия - осуществляется вызов по значению.
Значением параметра nargs является количество фактических параметров - фикси-
рованная величина. Благодаря ей можно установить, сколько фактических парамет-
ров реально передается в функцию с переменными параметрами со стороны вызыва-
ющей функции. Однако возможности ограничены тем, что можно знать только число
передаваемых параметров. При этом, установить тип каждого из этих параметров
невозможно.
Для того, чтобы организовать доступ к указанным параметрам, нужно описать
каждый из передаваемых при вызове функции параметров. Первый из параметров
(если он имеется) расположен по адресу массива args. Другими словами, значение
первого из параметров args присваивается идентификатору args. Второй из пара-
метров помещается по адресу, сдвинутому в сторону роста адресов памяти. Вообще
можно составить последовательность адресов:
адрес 2-го параметра = адрес args + 2
адрес 3-го параметра = адрес args + 4
В следующей таблице приведено соответствие параметров и их адресов:
________________________________________________
Номер параметра Адрес
________________________________________________
1 Адрес начала массива args
2 Адрес начала + 2
3 Адрес начала + 4
...
n Адрес начала + (n-1)*2
________________________________________________
Согласно соответствию адресов и последовательности параметров, приведенному
в таблице, можно организовать доступ к этим параметрам. Например, для того,
чтобы сослаться на первый и второй параметры типа "char", необходимо написать
в программе:
(char)args - ссылка на первый параметр типа "char"
(char)+(&args+1) - ссылка на второй параметр типа "char"
Примечание: Если в приведенном выше примере args является "int", то благодаря
описанию, значение &args преобразуется в тип "int". Адрес следующего по порядку
аргумента формируется согласно формуле &args+1, в которой константа "1" преоб-
разуется в соответствии с масштабом разрядной сетки конкретной машины: как пра-
вило, это два байта (вообще, можно определить эту величину с помощью выражения
sizeof(int*)). В конкретном случае это дает "адрес начала массива параметров
плюс два". Аналогичным образом можно организовать доступ к параметрам других
типов.
В заключение данного параграфа приведены два примера простых функций с
переменными параметрами: функция sum возвращает значение суммы переменного
числа "int" параметров. Функция cmax возвращает максимальное значение из ряда
"int" параметров в TINY (char), число которых - переменное.
#pragma nonrec
typedef char TINY;
int sum(.);
TINY cmax(.);
int sum(nargs, args)
int nargs, args
{
int i, *p;
for(i=0, p=&args; nargs --;)
i += *p++;
return i;
}
TINY cmax(nargs, args)
int nargs;
TINY args;
{
int *p, max;
for(max=0, p=(int*)&args; nargs--; p++)
if(max < (TINY)*p)
max = (TINY)*p;
return max;
}
Преобразования байтовой арифметики
Для повышения эффективности объектного кода, компилятор SOLID C выполняет
преобразования байтовой арифметики. Благодаря этой особенности, все преобразо-
вания переменных, имеющих тип "char", выполняются не со словами, а с байтами.
Поскольку процессор Z80 с восьмибитовым форматом команд выполняет эти операции
значительно быстрее, нежели другие. Байтовая арифметика особенно эффективна
при написании специальных программ, включающих байтовые преобразования.
Результаты логических операций и операций отношения
&& || ! = != > < >= <=
имеют тип "char".
Традиционно, те байтовые переменные, которые обозначают внешние кодовые
символы, специфицируются с помощью определения типа typedef в виде TINY или
BOOL. Первый определяет арифметические объекты, а второй - логические.
Определения указанных типов включены в файл стандартных заголовков "types.h".
Ниже даны несколько примеров употребления байтовых операций, на которые
следует обратить особое внимание!.
1). Переполнение и исчезновение
При выполнении байтовых арифметических операций, возможны случаи перепол-
нения или исчезновения. Например, в процессе выполнения операторов
int i;
TINY x,y;
x = 10; y = 30;
i = x * y;
вместо правильного значения 300, переменная "i" получает неправильное зна-
чение 44. Это происходит вследствие того, что в процессе выполнения умножения
по правилам байтовой арифметики теряется старший байт результата. Для того,
чтобы получить правильный результат (число 300), нужно оперировать с целыми
операндами, для чего следует специфицировать их с помощью функции cast, как
показано в следующем примере:
i = (int)x * (int)y;
Такое же явление возникает и в следующей ситуации:
i = x << 8 | y;
При выполнении операции сдвига над операндом байтовой длины, может быть
потеряна его часть вследствие переполнения. На это также нужно обратить внима-
ние. Величину "y" необходимо подставить в выражение для "i" в том же самом
виде, что и показано ниже:
i = (unsigned)x << 8 | y;
2). У типа TINY (char) отсутсвует знак числа. Переменные типа "char" исполь-
зуются без знака. Поэтому иногда возникают ситуации, в которых результат опе-
рации вычитания становится отрицательным. Поэтому, если выполнить ниже-следую-
щую программу, в переменную "i" будет записано 255:
int i;
TINY x,y;
x = 2;
y = 3;
i = x - y;
Для того, чтобы получить правильный результат, нужно выполнить эту операцию
в соответствии с типом "int":
i = (int)x - (int)y;
3). Согласование параметров
Компилятор SOLID C проводит четкое различие между типами "int" и "char".
Поэтому фактические и формальные параметры должны быть обязательно согласованы
между собой по типу. Следующий пример иллюстрирует неправильный вызов функции:
func(x)
TINY x;
{
...
}
main()
{
func(1); /* неправильный вызов функции */
}
Нужно изменить вызов следующим образом:
func((TINY)1);
Согласование параметров по типу, компилятором не проверяется. Поэтому,
следует обращать особое внимание на подобные ситуации.
Распределение регистров
Поскольку компилятор SOLID C поддерживает распределение регистровых пере-
менных, он способен генерировать более эффективный объектный код. В стандарт-
ном языке Си предусмотрено размещение непосредственно в регистрах (класс памя-
ти "register") только нескольких первых переменных из всего их множества.
(Какое именно количество регистровых переменных будет помещено в регистры,
зависит от того, какая рабочая машина будет использоваться). В отличие от
стандартного, компилятор SOLID C автоматически выполняет оптимальное распре-
деление регистров, в соответствии с потоком данных. Этим самым снята необхо-
димость обязательного указания переменных программы, как регистровых. Вопрос,
какие из переменных будут помещены в регистры, решается на уровне кодогенера-
ции. Поэтому описание "register" идентично пo смыслу описанию "auto" и никакого
другого смысла не имеет.
В каждой функции программы, первые 16 переменных простого типа становятся
кандидатами на размещение в регистрах, как переменные типов "аuto" или
"register". K числу таких простых типов относятся "int", "char", "unsigned"
и "указатель".
Средствами языка Си можно получать адреса переменных. Компилятор SOLID C
также, безусловно, располагает этими средствами. Однако, в случаях тех перемен-
ных, которые являются объектами распределения регистровой памяти, возникает
проблема. Она обусловлена тем, что как таковой "адрес" регистра не существует.
Например, если написать
int n;
n = 10;
scanf("%d", &n);
printf("%d", n);
и ввести 100, то какое значение получит переменная "n" ?. Конечно, 100.
Компилятор поместит значение переменной по заданному адресу в память, если
указать ему значение этого адреса. Следовательно, никаких проблем не возникнет,
если при вызове функции передать ей в качестве параметра адрес переменной.
Однако в примере
int n;
int *p;
p = &n;
n = 10;
*p = 100;
printf("%d", n);
возможна ситуация, когда в качестве значения переменной "n" будет индициро-
вано не 100, а 10. Между двумя предложениями, одно из которых "n=10", a другое
"printf("%d",n)", не фигурирует "&n". Поэтому компилятор не помещает "n" в па-
мять.
НЕКОТОРЫЕ ДРУГИЕ ОСОБЕННОСТИ КОМПИЛЯТОРА
Таблица представления данных различного типа
-----------------------------------------------------
Тип Разрядность Диапазон значений
-----------------------------------------------------
char 8 0..255
short 16 -32768..32767
int 16 -32768..32767
unsigned 16 0..65535
_____________________________________________________
1. В рамках компилятора SOLID C проводится идентификация переменных и
функций по первым 30-ти символам имени. Благодаря такому расширению имен,
можно использовать легко поддающиеся анализу имена переменных и функций, и
получать удобочитаемые тексты программ.
2. Позиция ошибки диагностируется по номеру ошибочной строки и начальному
символу (колонки в строке). Начальная позиция считается с 0. Таким образом,
позиция первого символа программного текста есть "нулевая строка, нулевая ко-
лонка".
3. Для указания имени включаемого файла используется предложение "#include"
препроцессора, как описано ниже:
<имя файла> - ссылка на файл, расположенный в подкаталоге "INCLUDE"
каталога компилятора.
"имя файла" - ссылка на файл, расположенный по текущему пути исходного
".c" файла.
Задание пути в имени файла не допускается. Например, если имеются предложения
#include <stdio.h>
#include "common.h"
то в первом случае сделана ссылка на стандартный файл компилятора Си, распо-
ложенного в подкаталоге с зарезервированным именем "INCLUDE" каталога SOLID C
(по-умолчанию "SOLID"), а во втором случае - на файл, расположенный по теку-
щему пути входного ".c" файла.
Следует обратить внимание на то, что имя подкаталога "INCLUDE" зарезервировано
и его не следует менять.
4. Типы long, float, double не поддерживаются, но зарезервированы. Поэтому
нельзя использовать слова "long", "float" и "double" в качестве идентифика-
торов переменных и меток.
5. Не поддерживаются битовые поля структур.
6. Некоторые ограничения в константных выражениях. Нельзя включать в
константные выражения указанных ниже типов унарную операцию "sizeof":
a) сразу после оператора "case" (в качестве метки);
b) в описателе массива в качестве его размерности.
Подобные ограничения не действуют на те константные выражения, которые
появляются в инициаторе (т.е. при инициализации).
7. Имеется отличие от стандартного языка Си по эффективным пределам имен
членов структур и объединений (struct & union). В стандартном Си одно и то же
имя члена может появляться в двух различных структурах только в случае, когда
они оба одного типа и имеют одинаковое смещение в обеих структурах. Компилятор
SOLID C воспринимает имя члена так, как принято в Паскале или в Аде. Таким об-
разом, одно и то же имя члена может появляться в разных структурах и будет
правильно обработано компилятором SOLID C.
В качестве примера рассмотрим следующее описание:
struct node {
char *word;
int count;
struct node *next;
} pool[1000], *p;
struct noad {
int atr;
struct noad *next;
} table[10], *q;
В стандартном языке Си данная запись является неправильной, потому, что имя
члена "next" появляется в двух структурах - "node" и "noad", где их типы и сме-
щения не согласованы. Однако в среде компилятора SOLID C эти записи являются
правильными.
Следует обратить внимание на следующую запись
p->next
которая описывает член типа "struct node" (потому что "p" есть указатель типа
"struct node"). Благодаря такому приему можно подбирать правильные члены путем
строгого указания их типов. Так, в операциях "." и "->" левые члены должны быть
указателями структур, включающих члены, записанные в качестве правой части этих
операций. Например:
i->count
включает "i" не как указатель типа "struct node", а как "int" (целое). Поэтому
компилятор SOLID C такую ситуацию не диагностирует. Для того, чтобы получить
такой же результат, необходимо переписать приведенное выражение следующим
образом:
((struct node *)i)->count
В приведенном выше примере для того, чтобы операция "->" была более высокого
ранга, нежели операция "cast", следует терм
(struct node *) i
заключить в скобки.
До того, пока не будет преобразован тип указателя в "int", что выполняется
с помощью функции cast, операция не может быть выполнена.
Даже при выполнении арифметических операций в случае указателей разных ти-
пов, также необходимо использовать функцию cast. Например, в случае описаний
int i;
char *p;
int *q;
операции
p = i;
if(p == i)
q = p;
if(p == q)
будут отмечены как ошибочные.
Две первые ошибочны потому, что производятся подстановки между типами указа-
теля и "int" (целого), и операции сравнения между ними. Две последние также
ошибочны, потому, что несмотря на то, что и "p", и "q" обе являются указате-
лями, но они специфицированы разными типами и поэтому компилятор отметит их,
как ошибочные.
Эти ошибки можно устранить, либо использованием функции cast, либо указанием
опции "-k" на первом проходе компилятора.
С другой стороны, разрешены преобразования типов из целого в указатель и
обратно. Это обусловлено тем, что в языке Си целая константа 0 используется
как указатель особого адреса. Например, запись
char *p;
if(p != 0)
p = 0;
не содержит ошибки.
Типы "int" и указателей нельзя смешивать в среде компилятора SOLID C. Дан-
ное соглашение вообще может считаться показателем хорошего стиля программиро-
вания, при котором отладка программы становится проще.
8. Нет автоматического преобразования между типами "указатель" и "int",
и обратно. В стандартном языке Си разрешены операции сравнения значений указа-
телей и целых (int), но в SOLID C смешивание указанных типов вызовет сообщение
об ошибке. Однако этот режим можно разрешить, указав опцию компилятора "-k"
(на первом проходе).
9. Тип "char" интерпретируется как значение без знака, т.е. может иметь
значение 0..255.
10. В рамках стандартного языка Си, в случаях появления необъявленных ранее
функций, они интерпретируются как возвращающие "int" значение. Параметры
необъявленного типа, которые вошли в список параметров, считаются "int".
Рассматриваемый здесь компилятор не позволяет использовать эти умолчания. То
есть, хороший стиль программирования предполагает описание всех идентификато-
ров до того, как на них будут выполняться ссылки - автоматические ссылки вы-
полняться не будут.
Все объявления, которые относятся к функциям стандартной библиотеки, запи-
саны в файле стандартных заголовков, поэтому любую из стандартных функций можно
использовать без объявления. Поэтому все пользовательские программы должны
содержать ссылку на файл "stdio.h" в самом начале текста, в предложении
#include <stdio.h>.
Однако, если на первом проходе компилятора указать опцию "-k", указанные
описания функций будут объявлены по-умолчанию. От этого теряется качество полу-
чаемого кода, поэтому данной опцией целесообразно пользоваться, если важно
обеспечить совместимость со стандартным языком Си.
11. В данной версии рассматриваемого компилятора не поддерживается предложе-
ние препроцессора "#if". Вполне возможно заменить указанное предложение дру-
гими: "#ifdef" или же "#ifndef". Возможны также другие варианты замены предло-
жения "#if". Рассматриваемый здесь компилятор не генерирует код тех участков
программы, выполнение которых не требуется при ее прогоне. Поэтому можно полу-
чить точно такой же результат, если применить оператор "if" вместо предложе-
ния "#if". Например, можно заменить текст одной из ниже-следующих программ на
другую:
Программа 1:
#if sizeof(FCB) != sizeof(char[36])
Open_MSX();
#else
Open_OTHER();
#endif
Программа 2:
if (sizeof(FCB) != sizeof(char[36]))
Open_MSX();
else
Open_OTHER();
12. Введена директива препроцессора "#pragma". Применяется для различных
указаний компилятору. Все предложения данного вида могут появляться в любом
месте исходной программы. Ниже, в качестве примеров приведены четыре предложе-
ния с заданием соответствующих директив компилятору:
1. #pragma optimize time
Данное указание компилятор интерпретирует так, что он старается сгенерировать
объектный код, отличающийся более высокой скоростью выполнения, нежели в слу-
чае, когда он должен быть более компактным.
2. #pragma optimize size
При таком указании, компилятор считает более важным сгенерировать компактный
объектный код, хотя бы и не очень быстрый в исполнении.
3. #pragma nonrec
Этой директивой изменяется задание по-умолчанию режима выполнения функций,
как нерекурсивных.
4. #pragma recursive
С помощью данного предложения устанавливается по-умолчанию режим выполнения
функций как рекурсивных.
13. Поскольку компилятор SOLID C поддерживает для переменных автоматическое
распределение регистров, класс памяти всегда устанавливается "auto" (автомати-
ческий).
14. Компилятор SOLID C позволяет гнездовать тексты комментариев. Эта функция
обеспечивает возможность написания больших программ, содержащих комментарии, в
теле которых, в свою очередь содержатся комментарии. Если на первом проходе
указать компилятору опцию "-c", гнездование комментариев производиться не бу-
дет, что отвечает возможностям стандартного языка Си.
ПЕРЕДАЧА ПАРАМЕТРОВ
Функции с фиксированными параметрами
В случае обычных функций (функций с фиксированными параметрами), первые три
параметра помещаются в регистры, а все последующие - в стек. Если какой-либо
из 3-х параметров имеет тип "char", он помещается в соответствующий 8-ми битный
регистр. Параметры (первые три) других типов помещаются в регистровые пары.
4-й параметр (если есть) и все последующие - помещаются в стек, в обратном по-
рядке (паскалевский способ ?). На вершину стека помещается адрес возврата.
Каждый из размещаемых в стеке параметров занимает два байта. Значение "char"
параметра помещается в младший байт, а значение старшего байта остается неопре-
деленным.
В режиме совместимости со стандартным языком Си, использование параметра типа
"char" имеет отличия: преобразование из "char" формата в "int" производится
автоматически (при этом старший байт обнуляется), после чего данные рассматри-
ваются как "другой тип".
_______________________________________________________________________
Тип параметра Первый Второй Третий Четвертый Пятый ...
_______________________________________________________________________
char A E C (SP+2) (SP+4) ...
другой тип HL DE BC (SP+2) (SP+4) ...
(SP+3) (SP+5) ...
_______________________________________________________________________
Функции с переменными параметрами
Когда вызывается функция с переменными параметрами, все параметры заталки-
ваются в стек (в обратном порядке). Число параметров, помещенных в стек, запи-
сывается в регистровую пару HL. На вершину стека помещается адрес возврата.
В "собственном" режиме, "char" параметр помещается в младший байт, значение
старшего байта остается неопределенным.
В режиме совместимости со стандартом языка Си, "char" тип автоматически
преобразуется в "int" тип (при этом старший байт обнуляется), который далее
трактуется как "другой тип".
Параметры других типов размещаются в стеке согласно правилам стандарта для
процессора Z80, а именно: первым запоминается младший байт, вторым - старший.
_______________________________________________________________________
Тип параметра Количество Первый Второй Третий ...
_______________________________________________________________________
char HL (SP+2) (SP+4) (SP+6) ...
другой тип HL (SP+2) (SP+4) (SP+6) ...
(SP+3) (SP+5) (SP+7)
_______________________________________________________________________
ВОЗВРАТ ЗНАЧЕНИЯ ФУНКЦИИ
При передаче управления вызывающей программе, функция может передавать свое
значение через регистры. При этом функция типа "char" передает значение через
регистр "A". Функции других типов передают свои значения через регистровую
пару HL.
В режиме совместимости со стандартным языком Си, возвращаемое значение всегда
передается через регистровую пару HL, даже в том случае, когда функция имеет
"char" тип.
Для возврата в вызывающую функцию двух и более значений, необходимо исполь-
зовать указатели переменных, как параметры любых других си-программ.
ПРАВИЛА ВЫЗОВА ФУНКЦИЙ
1. Во-первых, вызывающая программа оформляет параметры и помещает их в ре-
гистры или заталкивает в стек согласно правилам, описанным выше.
2. Программа вызывает функцию. Для вызова обычно используется слово "call".
3. После возврата управления функцией, параметры выталкиваются из стека
(с использованием команды "pop"). Если все параметры размещены в регистрах,
использовать команду "pop" нет необходимости. Следует отметить, что в случае
применения команды "pop", возвращаемые через регистры "A" или "HL" значения
функций не будут потеряны.
4. Значение функции возвращается через регистр "A", если функция имеет
"char" тип. Для функций другого типа, значение всегда возвращается через
регистровую пару HL.
ПЕРЕДАЧИ УПРАВЛЕНИЯ ВЫЗЫВАЕМОЙ ФУНКЦИИ
Вызываемая функция не должна разрушить содержимое указателя стека. Иными
словами, содержимое указателя стека при выходе из функции должно остаться тем
же, что и при входе в нее. Параметры, загруженные в стек, должны выталкиваться
из него вызывающей программой. Вызываемая функция может изменить значения па-
раметров (как тех, которые передаются через регистры, так и загруженных в стек).
Однако следует подчеркнуть, что в языке Си действует соглашение, в рамках кото-
рого вызов функций осуществляется по значению, поэтому даже если будут возвра-
щены измененные параметры, это никакого влияния на процесс вызова функции не
окажет.
Возвращаемое значение функции помещается в регистр "A" или в регистровую
пару HL - в зависимости от типа вызванной функции. Эти правила описаны в пре-
дыдущем разделе.
Пример 1:
В следующем примере объявлена ассемблерная функция ISDIGIT, которая возвра-
щает значение функции BOOL в регистре "A". Вызывающая функция передает параметр
"char" типа через регистр "A".
... вызывающая программа (на языке Си)...
typedef char BOOL; /* определение "char" типа как BOOL */
...
BOOL isdigit(c);
char c;
...
if (isdigit(c))
...
...
... вызываемая функция (на языке Ассемблера)...
public isdigit_
isdigit_:cp '0'
jr c,false
cp '9'+1
ret c ; цифра, вернуть true
false: xor a ; не цифра, вернуть false
ret
Пример 2:
Этот пример соответствует случаю вызова функции, написанной на Си, из ас-
семблерной процедуры.
Стандартные библиотечные функции printf и puts вызываются из ассемблерной
процедуры. Их исходные тексты находятся в исходниках библиотеки, поэтому здесь
функции printf и puts не будут описаны. Следует заметить, что поскольку функ-
ция printf - с переменными параметрами, ее параметры заталкиваются в стек.
В данном примере параметр всего один, однако в случаях двух и большего числа
параметров, они помещаются в стек в обратной последовательности. Число парамет-
ров передается в вызываемую функцию через регистровую пару HL (в приведенном
примере передается число 1). После возврата из вызываемой функции, будут выпол-
няться операции выталкивания из стека (в примере команда "pop" будет выполнена
1 раз). Следует помнить, что параметры восстанавливаются из стека вызывающей
программой, а не вызываемой.
С другой стороны, функция puts принадлежит к числу функций с фиксированными
параметрами, поэтому адрес символьной строки передается через регистровую пару
HL.
extern printf_, puts_
example: ld hl,msg
push hl ; поместить в стек адрес символьной строки
ld hl,1 ; загрузить число параметров
call printf_ ; вызов процедуры, формирующей вывод
pop hl ; выталкивание параметров из стека
;
ld hl,msg ; загрузить адрес строки
jp puts_ ; вызов функции вывода строки
msg: db 'Hello, world!',0Ah,0
РЕКОМЕНДАЦИИ ПО ПРОГРАММИРОВАНИЮ
1. Следует пользоваться нерекурсивными функциями, если нет особой необхо-
димости использовать рекурсию. Благодаря такому подходу, можно достичь более
быстрого доступа к тем локальным переменным, для которых не распределяется
регистровая память и следовательно, получить эффективный объектный код.
2. Если указан режим совместимости со стандартным языком Си (опция "-k"),
то качество объектного кода будет ниже, поэтому указание на этот режим нужно
применять только в случаях, когда требуемая совместимость на уровне стандарт-
ного языка Си нужна больше, чем эффективность кода.
3. Используйте тип "char" (с 8-битовой длиной данных) везде, где только
можно. Байтовые операции выполняются с помощью 8-битового процессора более
эффективно, чем над данными, длиной в слово (типы "int" или "unsigned").
Поэтому определяйте переменные, числовые значения которых не выходят за пре-
делы диапазона 0..255, как TINY (char).
4. При использовании 16-битовых переменных следует определять их преимущест-
венно типом "unsigned", который в этих случаях предпочтительнее, нежели "int".
Это обусловлено тем, что в процессоре Z80 выполнение операций со знаком (в част-
ности, операций сравнения величин со знаком) реализовано не лучшим образом.
5. Нежелательно установление статического режима для локальных переменных.
Переменные, определенные как статические или внешние, не подлежат загрузке в
регистровую память. Поэтому, если для повышения скорости выполнения программы
определить локальные переменные как "static", а функции как "extern", то
результат будет противоположным - эффективность объектного кода существенно
понизится. Компилятор SOLID C сам производит распределение регистров, поэтому
следует обязательно определять локальные переменные, как "auto".
ЗАРЕЗЕРВИРОВАННЫЕ СЛОВА ЯЗЫКА
Следующие слова используются как ключевые в компиляторе SOLID C и их нельзя
использовать в качестве меток или имен переменных.
asm float signed
auto for static
break goto struct
case if switch
char int typedef
const long union
continue nonrec unsigned
default recursive void
do register volatile
double return while
else short
extern sizeof
ДИРЕКТИВЫ КОМПИЛЯТОРА
define nonrec
endif optimize
error pragma
ifdef recursive
ifndef size
include time
line undef
+2570
View File
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
ПРИНЦИП РАЗДЕЛЬНОЙ КОМПИЛЯЦИИ
При разработке больших программ исходный текст разделен на несколько файлов.
После того, как они отдельно друг от друга откомпилированы, их нужно скомпоно-
вать в единую программу. Здесь описан метод раздельной компиляции и отмечены
основные моменты, на которые следует обращать внимание при разработке исходного
текста.
1. Замечания к исходной программе
Имеются две серьезные проблемы раздельной компиляции и сборки больших
программ:
- ссылки на функции из других файлов
- возможность получения общих данных
Ниже дана попытка освещения этих вопросов с параллельным показом на приме-
рах. Рассмотрим следующую программу.
#include <stdio.h>
int a[10];
char b[4];
char main()
{
char sub1(),sub2();
sub1();
sub2();
printf("%d,%d,%d\n",a[0],a[3],a[9]);
printf("%c,%c,%c\n",b[0],b[1],b[2]);
}
char sub1()
{
a[0]=1;
a[3]=2;
a[9]=3;
}
char sub2()
{
b[0]='a';
b[1]='b';
b[2]='c';
}
Текст данной программы размещен в трех файлах с именами PROG1, PROG2 и
PROG3 - соответственно содержащих главную функцию и функции 1 и 2.
Точнее, в каждом из этих файлов содержатся:
PROG1.C:
#include <stdio.h>
int a[10];
char b[4];
char main()
{
:
:
}
PROG2.C:
extern int a[10];
char sub1()
{
:
:
}
PROG3.C:
extern char b[4];
char sub2()
{
:
:
}
Функция main вызывает функции sub1 и sub2, поэтому их определения необхо-
димы. Так как функции sub1 и sub2 в свою очередь никакие другие функции не
вызывают - никаких других описаний не требуется. Использование массивов "a"
и "b" также является проблемой. На идентификатор "а" ссылаются программа main
и sub1. На массив "b" ссылаются функции main и sub2. В любом из этих вариантов
между файлами должна быть установлена связь.
Для этой цели используется описатель "extern" (внешний). В первом из файлов
- PROG1.C, этот описатель отсутствует, однако в двух других файлах этот описа-
тель введен. Этот факт отражает то обстоятельство, что массивы "a" и "b" содер-
жатся в файле PROG1.C, а в других файлах этих массивов нет. Если в двух или
большем числе файлов будут стоять определения без описателей "extern", то при
сборке единой программы появятся сообщения о дублировании имен массивов. С
другой стороны, при добавлении описателя "extern" во все определения, они мо-
гут диагностироваться как неопределенные.
Следовательно, в случае организации общих данных для нескольких файлов необ-
ходимо дать определение без описателя "extern" только в одном из файлов с
текстом исходной программы. Во все другие файлы нужно поставить "extern".
2. Процедура компиляции и сборки программы
Для описанных выше текстовых файлов с исходной программой последователь-
ность команд компиляции и сборки модулей выглядит следующим образом:
cc1 prog1
cc1 prog2
cc1 prog3
cc2 prog1
cc2 prog2
cc2 prog3
as prog1
as prog2
as prog3
ld prog=prog1,prog2,prog3,clib/l/gXMAIN
В том случае, когда все части программы (все файлы) уже откомпилированы и
требуется лишь перекомпилировать одни из них (например файл PROG2.C), следует
выполнить приведенную ниже группу команд:
cc1 prog2
cc2 prog2
as prog2
ld prog=prog1,prog2,prog3,clib/l/gXMAIN
+228
View File
@@ -0,0 +1,228 @@
СОЗДАНИЕ БИБЛИОТЕК
Известны два способа создания библиотеки. Оба они будут описаны в следующих
параграфах. Первый из них, метод "а" - быстрый и легкий по исполнению, однако
к собираемым пользовательским программам прилинковываются в том числе и такие
функции, которые не обязательно будут использоваться.
В отличие от первого, способ "b" реализуется сложнее и требует большего вре-
мени, но получаемые в соответствии с ним пользовательские программы будут со-
держать в себе только те функции, которые заведомо применяются в программе.
Стандартная библиотека компилятора "clib.irl" создается именно по способу "b".
1. Простой способ создания библиотеки
Рассмотрим пример двух функций "power" и "log2":
int power(x,e)
int x,e;
{
int y;
for(y=1; e>0; e--)
y *= x;
return(y);
}
int log2(x)
int x;
{
int y;
for(y=0; x>1; x>>=1)
y++;
return(y);
}
Если эти функции помещаются в файл с именем XLIB.C, последовательность
создания библиотеки выглядит следующим образом:
cc1 xlib
cc2 xlib
as xlib
При выполнении этой последовательности будет создана библиотека с именем
XLIB.REL. Ниже приведена команда сборки программы, которая вызывает функции
из этой библиотеки:
ld prog,xlib,clib/l/gXMAIN
В этом случае нет необходимости писать ключ /l после имени библиотеки XLIB,
т.к. даже если его написать, тем не менее все функции, входящие в библиотеку
XLIB, будут скомпонованы с программой "prog".
Здесь будет описан способ включения в библиотеку программ, написанных на
Ассемблере. В качестве примеров взяты функции hex, ror и rol, помещенные в
файл ALIB.ASM:
public hex_, ror_, rol_
cseg
hex_: ; convert 0..F -> '0'..'F'
and 0Fh
add a,90h
daa
adc a,40h
daa
ret
ror_: ; ror(c,n) n bit rotate right char
inc e
rorl: dec e
ret z
rrca
jp rorl
rol_: ; rol(c,n) n bit rotate left char
inc e
roll: dec e
ret z
rlca
jp roll
Включение этих функций в библиотеку производится путем выполнения следующей
команды:
as alib.asm
Следующая команда вызывает линковку программы, как и в случае Си-программы:
ld prog,alib,clib/l/gXMAIN
Как видно из приведенных выше примеров, самый простой способ формирования
библиотеки - включение в нее тех программ, из которых компонуется программа.
Однако этот способ не совершенен, так как вызывает подключение всей библиотеки.
В следующем параграфе будет описан способ создания библиотеки, при котором
программа компонуется только из тех функций, к которым предполагаются обраще-
ния.
2. Создание полной библиотеки
2.1. Включение Си-программ в библиотеку
Здесь будет приведен тот же пример, что и выше - включение файла XLIB.C,
содержащего тексты функций POWER и LOG2 в библиотеку.
Вначале нужно "разделить" файл XLIB.C на составляющие его функции, записав
их в отдельные файлы POWER.C и LOG2.C. Далее необходимо выполнить следующие
команды:
cc1 power
cc1 log2
cc2 power
cc2 log2
as power
as log2
ol a xlib.irl power log2
При выполнении этой последовательности команд, происходит следующее:
1. Си-тексты функций транслируются в ассемблерные листинги.
2. В результате ассемблирования получаем модули с расширением ".rel".
3. С помощью библиотекаря OL из двух модулей с расширением ".rel"
создается библиотека XLIB.IRL.
После создания библиотеки XLIB.IRL, файлы POWER.REL и LOG2.REL уже не нужны
и их можно удалить.
Применение библиотеки XLIB.IRL определяется следующей командой:
ld prog,xlib/l,clib/l/gXMAIN
После имени библиотеки XLIB нужен ключ /l как указание линкеру подключить
к программе только необходимые ей функции.
При сборке программ, состоящих из большого числа файлов (функций), имеет
значение порядок расположения функций. Ниже приведены два примера: один из
них правильный, а другой неправильный.
Правильный пример:
char islower(c)
char c;
{
return('a'<=c && c<='z');
}
char toupper(c)
char c;
{
return(islower(c) ? c-'a'-'A' : c);
}
В данном примере в библиотеку включается файл X.C, состоящий из двух
функций. В этом случае функция "islower" расположена в файле X.REL перед
функцией "toupper". Поэтому при редактировании связей по ключу /l линкер
не выдаст диагностику об ошибке типа отсутствия определения.
Но если написать вначале текст функции "toupper", возникает следующий
неправильный пример:
char toupper(c)
char c;
{
char islower();
return(islower(c) ? c-'a'-'A' : c);
}
char islower(c)
char c;
{
return('a'<=c && c<='z');
}
В результате расположение модулей в библиотечном файле будет неправильным.
Подводя итоги сказанному, можно резюмировать, что для правильного порядка
расположения модулей в библиотеке нужно в исходной программе вначале писать
вызываемую функцию, а после нее - вызывающую.
2.2. Создание библиотеки модулей на Ассемблере
По сравнению с описанием включения в библиотеку, которое было дано в п.1,
здесь, файл ALIB.ASM следует предварительно разделить на следующие три файла
"hex.asm", "ror.asm" и "rol.asm":
HEX.ASM:
cseg
hex_:: and 0Fh
add a,90h
daa
adc a,40h
daa
ret
ROR.ASM:
cseg
ror_:: inc e
rorl: dec e
ret z
rrca
jp rorl
ROL.ASM:
cseg
rol_:: inc e
roll: dec e
ret z
rlca
jp roll
После того, как каждый из них будет ассемблирован и получен файл с расши-
рением ".rel", вызывается библиотекарь OL для создания библиотеки:
as hex
as ror
as rol
ol a alib.irl hex ror rol
Применение библиотеки ALIB.IRL определяется следующей командой:
ld prog,alib/l,clib/l/gXMAIN
При сборке программы, точно таким же образом, как и для сборки Си-программы
из модулей, должна соблюдаться последовательность записи исходного текста, а
именно: вызываемая функция должна быть записана прежде, а вызывающая ее, вслед
за ней.
+80
View File
@@ -0,0 +1,80 @@
Последняя редакция: 7.05.2004
IRL формат
~~~~~~~~~~
IRL-файл представляет собой индексированный REL-файл. Благодаря индекси-
рованию улучшается работа линкера. В начале IRL-файла находятся 3 байта:
db старший ;+0 (File offset >> 14) & 0x7F
db средний ;+1 (File offset >> 7) & 0x7F
db младший ;+2 (File offset 1) & 0x7F
IRL-файл состоит из 3 секций: IRL-заголовок, Символьная таблица и REL-имидж.
IRL-заголовок
~~~~~~~~~~~~~
Массив 128 байт в начале REL-файла. Используются только первые три байта,
содержащие смещение до REL-имиджа. Остальные байты установлены в ноль:
db старш,средн,0 ; Смещение до REL-имиджа. Младший байт
; всегда равен 0. Начало REL-имиджа вы-
; равнивается на границу в 128 байтов.
ds 125 ; Не используются и установлены в 0.
Чтобы вычислить смещение до REL-имиджа, надо "старший" и "средний" байты
умножить на 128. Например для файла clib.irl: 0011h * 128 = 0880h
ld h,xx ;+0
ld a,xx ;+1
;a,h,l * 128
add a,a
ld l,a
xor a
srl h
rr l
srl h
rr l
rra
;hl=ст.разряд
;a=мл.разряд
Обратите внимание, что 7-й бит первого байта всегда 0. В не индексированном
rel-файле, 7-й бит первого байта всегда 1 (первый бит записи "имя модуля").
Символьная таблица
~~~~~~~~~~~~~~~~~~
Таблица начинается со смещения +80h от начала файла и состоит из множества
символьных записей разной длины. Каждая запись имеет формат:
db ст,средн,мл ; Смещение от начала rel-имиджа до rel-модуля,
; содержащего символьную строку (имя).
db "SYMBOL" ; Символьная строка из 1..8 символов.
db 0FEh ; Конец строки.
Конец таблицы отмечается 0 в начале символьной строки:
db ст,средн,0 ; Размер REL-имиджа. Это не смещение на конец
; файла в REL-имидже, а показывает число байт
; REL-имиджа, включая выравнивание до границы
; в 128 байт.
db 0FEh ; Конец строки.
Примечание!: У irl-файлов от библиотекаря "OL", данная запись имеет формат:
db 0,0,0
db 0FFh
После этой записи, таблица выравнивается (LIB использует байт 1Ah) до следу-
ющей границы в 128 байт.
REL-имидж
~~~~~~~~~
REL-имидж всегда начинается с границы в 128 байт. Состоит из обычных rel-
данных. Чтобы преобразовать IRL-библиотеку в REL-библиотеку, надо удалить
заголовок и символьную таблицу.
+138
View File
@@ -0,0 +1,138 @@
Линкер
Линкер LD предназначен для сборки готовых программ или dll-библиотек
из отдельных объектных модулей. В качестве объектных модулей могут высту-
пать файлы rel-формата или библиотеки irl-формата (создаваемые библиотека-
рем ol.exe). Описание "rel", "irl" форматов смотрите в файлах "rel.rus" и
"irl.rus".
Строка вызова линкера имеет следующий формат:
LD [/keys] [outfile=] infile[/keys] [,infile[/keys] ...] [@ list]
где:
keys - опции линкера
outfile - имя выходного файла программы
infile - файл "rel" или "irl" формата
@ list - файл подстановок
Пример вызова линкера:
LD hello.rel
После выполнения данной команды получится файл "hello.exe".
При своей работе, линкер может создавать на диске временные файлы. Поэтому
для ускорения работы линкера рекомендуется использовать его на электронном
диске.
Опции линкера:
A использовать диск для сохранения симв. таблицы
D## адрес расположения DATA сегмента
E не вставлять EXE заголовок
Glabel задать метку 'label' в качестве стартовой
L поиск библиотечного файла (опция после имени файла)
P## адрес расположения CODE сегмента
Q вывести метки вида ?labels на экран
R## установить адрес загрузки (адрес ORG-а программы)
S вывести список меток на экран
T[type:ver] создать DLL библиотеку. Type - внутр. описание библы;
Ver - версия библы. Символ ":" разделяет параметры.
U заменить все "@" на "_" в именах меток
Y исключить DATA сегмент из выходного кода
X не создавать SYM файл
Создание DLL-библиотек
Для создания динамических библиотек служит опция "t". Обратите внимание,
что эта опция отменяет вставку exe-заголовка.
Отслеживается превышение максимального размера dll-библиотеки в 16 kB.
В этом случае линкер выдает предупреждающее сообщение, но линковка библи-
отеки не прерывается.
Создаваемые dll-библиотеки имеют сигнатуру заголовка "L1". Библиотека этого
формата отличается от формата "L0" только тем, что таблица перемещений начи-
нается сразу за концом "своего" кода.
Заголовок dll-библиотеки создается и прилинковывается автоматически, поэто-
му не надо резервировать для него место при написании ассемблерных текстов.
Более подробную информацию по dll-библиотекам смотрите в документации менед-
жера dll-библиотек.
Пример строки вызова линкера:
LD /TSymple library:0001 test.rel
где:
"Symple library" - Строка внутреннего описания библиотеки.
Строка описания может содержать пробелы.
Длина описания не должна превышать 16
символов, иначе линкер выдаст ошибку.
"0001" - Внутренний номер версии библиотеки. Может
содержать символы 0..9 и A..F (a..f).
"test.rel" - объектный файл, служащий для создания библиотеки.
Можно не указывать один из параметров (или все) опции, но символ ":" разде-
лителя параметров должен стоять всегда. В случае отсутствия описания - в
заголовке библиотеки будет стоять пустая строка. В случае отсутствия номера
версии - в заголовке будет стоять нулевой номер. Примеры вызова опции:
LD /Tsymple library: test.rel
LD /tМоя библиотека: test
LD /T:010B test.rel
LD /t:7 test
LD /t: test.rel
Обратите внимание, что строка внутреннего описания библиотеки начинается
сразу же за буквой "t" опции. Например, если написать
LD /T Example:0001 test.rel
то строка описания в заголовке библиотеки будет иметь вид " Example", т.е.
начинаться с пробела.
Файл подстановок
Файл подстановок служит для некоторой "автоматизации" процесса линковки
программ. Можно один раз создать файл подстановки и линковать программу, не
набирая каждый раз (возможно длинную) строку параметров линкера.
Файл подстановок может содержать всего одну строку агрументов или отдельные
аргументы могут начинаться с новой строки. В конце файла подстановок должна
стоять пустая строка. Размер файла подстановок ограничен до 256 байт и файл
может иметь любое имя.
Пример вызова линкера с файлами подстановок:
LD @ comp.txt
или
LD @list
Примеры файла подстановок.
Пример 1
test=prog,clib.irl/l/gxmain/x
Пример 2
test=prog,
clib.irl/l
/gxmain
/x
В данных примерах собирается программа с заданным именем "test". При лин-
ковке используется исходный объектный файл "prog.rel", библиотека "clib.irl"
и управление в программе передается на метку "xmain" (это необходимо делать
для всех программ, линкуемых с Си-библиотекой "clib.irl"). Опция "x" запре-
щает создание sym-файла.
+1217
View File
File diff suppressed because it is too large Load Diff
+1731
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
Библиотекарь
Библиотекарь OL служит для работы с библиотеками IRL-формата. Информацию
по этому формату можно прочитать в файле "irl.rus". Строка вызова библио-
текаря имеет следующий формат:
OL <опция> <имя_библиотеки> [<имя_модуля1> <имя_модуля2> ...]
Например:
OL a mylib.irl module1 module2 module3
В данном примере в библиотеку MYLIB.IRL добавляются три новых модуля
(rel-файлы). Если файла MYLIB.IRL не существует, то создается новая
библиотека с таким именем, содержащая три указанных модуля.
Опции библиотекаря:
A - Добавить модуль(и) в библиотеку. Если указанной библиотеки
нет, она создается.
D - Вывод дампа содержимого библиотеки на экран.
E - Извлечь модуль(и) из библиотеки и сохранить его в файле.
В именах модулей можно использовать глобальные символы
"*" и "?".
L - Вывод списка модулей библиотеки на экран.
R - Переиндексация библиотеки.
T - Проверка всех модулей библиотеки на неопределенные внешние
метки.
+155
View File
@@ -0,0 +1,155 @@
Некоторые особенности пакета SOLID C для ОС Estex
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
СИ-КОМПИЛЯТОР
Коды возврата компилятора (1-й и 2-й проходы):
00h - Ok
0FFh - были errors/warnings, ошибки выделения памяти или работы с файлами.
1-й проход компилятора
1. При компиляции "чужих" исходников ставить опцию "-k".
2. В опциях: -dNAME, -jN, -oNAME и -rP:S:H пробелы не допускаются.
3. Максимальная длина строки полного пути (disk\dir\file.ext) файлов
равна 80 символов.
4. Файл "file.err" создается всегда по текущему пути входного .c файла.
5. Формат записи include-файлов только как:
#include <file.ext> - поиск ведется только в подкаталоге "INCLUDE"
расположения компилятора.
#include "file.ext" - поиск ведется только в каталоге расположения
входного .c файла.
Задание пути в имени файла не допускается!. Имя папки "INCLUDE" зарезерви-
ровано.
2-й проход компилятора
1. Позволяет компилировать несколько бОльшие исходники, по сравнению
с фирменной версией.
2. Исправлены две фирменные ошибки:
a) нельзя было указывать расширение для TMC-файлов
b) зависание при компиляции запредельно больших исходников
АССЕМБЛЕР
Примечание: Поскольку ассемблер AS совместим с ассемблером M80 фирмы Microsoft
по формату записи ассемблерных текстов - в качестве документации на AS исполь-
зуется документация ассемблера M80. Отличие ассемблера AS от M80 заключается в
том, что у AS отсутствуют некоторые команды M80 (в основном, управление вывода
листингов).
1. Максимальная длина имен (включающих букву диска и каталоги) файлов
равна 64 символа.
2. В псевдокоманде NAME('...') можно использовать также двойные (")
кавычки.
3. Имя include-файла можно указывать без расширения. В этом случае будет
использоваться расширение по-умолчанию ".asm". Это же правило действует
и для входного asm-файла.
4. Поддерживаются три варианта записи недокументированных регистров:
HX/XH LX/XL
HY/YH LY/YL
HIX/IXH LIX/IXL
5. Псевдокоманды "EQU" и "=" идентичны.
6. Максимальная длина имен глобальных меток, передаваемых в объектный
файл (rel-файл) зависит от режима работы ассемблера:
6 символов - при указании опции "-t"
8 символов - по-умолчанию
30 символов - при указании опции "-x" (расширенный rel-формат)
Следует отметить, что с объектными файлами расширенного rel-формата не
работает библиотекарь. Поэтому при компиляции библиотечных файлов, не
следует ассемблеру указывать опцию "-x".
7. Коды возврата ассемблера:
00h - Ok
0FFh - были errors/warnings, ошибки выделения памяти
или работы с файлами.
ЛИНКЕР
При линковке готовых си-программ, собираемых с библиотекой "clib.irl" необхо-
димо всегда указывать линкеру опцию "/gXMAIN". Она передает управление на
начальный startup-код си-программ. См. исходники библиотеки "clib".
1. Линкер работает только с именами входных файлов. Указание диска и пути
расположения файлов не поддерживается.
2. Исправлены фирменные глюки:
a) неверная работа с "@list" файлом
b) не отслеживались запросы на request-файлы
3. При вставке exe-заголовка, параметры равны: ORG = 4100h, Stack = 0C000h.
(Значение ORG-a может быть изменено опцией "/r").
4. Файл "list" - содержит одну ком-строку с агрументами. Отдельные аргументы
могут начинаться с новой строки. В конце файла должна стоять пустая новая
строка. Кол-во читаемых байт ограничено до 256 байт.
5. Поиск библы: сперва файл *.irl, после *.rel. Если не найден, выдается
сообщение ошибки: "файл *.rel не найден".
6. Максимальная длина имен идентификаторов - 30 символов.
7. Метки вида ?labels не выводятся в sym-файл и на экран. Становятся
"видимыми" только при опции /Q.
8. Отслеживается превышение размера 16kB для dll-библиотек (выдается
предупреждение).
9. Коды возврата линкера:
00h - Ok
0FFh - были ошибки выделения памяти или работы с файлами.
БИБЛИОТЕКАРЬ
1. Тип открываемого входного файла отслеживается не по его расширению,
а по внутреннему содержимому.
2. При создании библиотеки: если имя библиотеки совпадает с именем входного
файла, то необходимо указать расширение библиотеки, чтобы не было двойного
добавления файлов. В именах rel-файлов расширение можно не указывать. Имена
добавляемых файлов разделяются пробелами или запятыми.
3. Глобальные символы "*" и "?" поддерживаются только для режима извлечения
модулей (опция "e"). В остальных режимах необходимо указывать реальное имя
файла.
4. Задание пути поддерживается только для файла библиотеки. Для rel-файлов
указание пути не поддерживается и используется путь расположения библиотеки.
5. В опции l(ist) (список модулей библиотеки), размеры "Code size: ..." и
"Data size: ..." выводятся в десятичном формате.
6. В опциях d(ump) и l(ist) вывод на экран можно остановить/продолжить при
нажатии на любую клавишу. При нажатии на Esc - выход из режима.
7. Коды возврата библиотекаря:
00h - Ok
0FFh - были ошибки работы или операций с файлами.
+137
View File
@@ -0,0 +1,137 @@
Последняя редакция: 7.05.2004
ФОРМАТ REL-СОВМЕСТИМЫХ ОБЪЕКТНЫХ ФАЙЛОВ
Объектные файлы REL-формата фирмы Microsoft, представляют собой битовый
поток. Отдельные поля в битовом потоке не выровнены на границу байта,
кроме элементов, описанных ниже.
Использование битового потока для объектных файлов уменьшает их размер,
сводя к минимуму число обращений к диску для чтения/записи данных.
Имеются два основных типа: Абсолютный и Перемещаемый. Первый бит служит
индикатором типов. Если он равен 0, следующие 8 бит загружаются как абсо-
лютный байт. Если первый бит равен 1, следующие 2 бита определяют 4 типа:
00b Спец-элемент (описание ниже).
01b Перемещаемый Код. Значение следующих 16 бит (2 байта)
необходимо прибавить с текущему значению счетчика адреса
Кода.
10b Перемещаемые Данные. Значение следующих 16 бит (2 байта)
необходимо прибавить с текущему значению счетчика адреса
Данных.
11b Перемещаемая Общая область (код+данные). Значение следующих
16 бит (2 байта) необходимо прибавить с текущему значению
счетчика адреса Common-области.
Спец-элемент(ы) содержит:
■ контр. поле из 4-х бит номеров элементов 0..15.
■ не обязательное A-поле. Содержит 2 бита типа адресации cseg,
dseg,common (кроме абсолютного) и 2 (4 у extended REL) байта
значения.
■ не обязательное B-поле. Содержит 3 бита (5 у extended REL)
длины имени и самого имени идентификатора.
Общее представление спец-элемента:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 00b xxx yy nnnn mmmm zzz + имя идентификатора
-------------- ------------------------
A-поле B-поле
xxx 4 бита контр. поля (0..15 номера элементов, см. ниже)
yy 2 бита типа адресации (cseg/dseg/common)
nnnn 16 бит значения (адрес)
mmmm 16 бит дополнительно, при расширенном REL-формате
zzz 3 бита длины имени (5 бит при расш. REL-формате)
... имя идентификатора
Номера элементов контрольного поля
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Номера элементов только в B-поле:
0 - имя метки, на которую имеется ссылка
1 - имя блока COMMON
2 - имя прогр. модуля (program name)
3 - request-имя файла (request library search)
4 - элемент расширения. См. примечание.
Номера элементов в A-поле и B-поле:
5 - размер COMMON (элемент только для A-поля ?)
6 - внешняя цепочка:
для A-поля: адрес головной цепочки
для B-поля: внешнее имя
7 - точка входа метки:
для A-поля: адрес располож. метки
для B-поля: имя метки
Номера элементов только в A-поле:
8 - External - offset. Used for JMP and CALL to externals.
9 - External + offset. The A value will be added to the two
bytes starting at the current location counter immediately
before execution.
10 - размер области Данных.
11 - счетчик памяти сегмента (ORG cseg/dseg/common).
12 - адрес цепочки. A is head of chain, replace all entries
in chain with current location counter.
Вход последней цепочки имеет нулевой адрес.
13 - размер области Кода.
14 - конец программного модуля (end program), далее идет
выравнивание до границы байта.
Этот элемент не содержит ни A-поле, ни B-поле:
15 - конец файла
Примечание по элементу расширения:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C-поле элемента расширения имеет формат B-поля спец-элемента, но область
имени не содержит имя идентификатора, как в B-поле. Вместо него здесь
находится один байт типа (сигнатура) элемента расширения, далее следует
от 1 до 7 байт дополнительной информации.
Таким образом, каждый элемент расширения имеет формат:
1 00b 0100b zzz i jjjjjjjj
----------------
C-поле
zzz любые 3 бита (000b представляется как 8).
(001b у асма SOLID).
i 8 бит типа (сигнатура) элемента расширения.
(0FEh у асма SOLID).
jjjjjjjj zzz-1 число байт информации. Значение зависит
от типа i.
Это присутствует только в одном элементе расширения:
zzz 010b (2)
i X'35' тип (сигнатура) оверлейного сегмента COBOL
j номер COBOL-сегмента - 31h (цифровой формат номера)
Когда линкеру встречается сигнатура оверлейного сегмента, текущее число
оверлейных сегментов устанавливается в значение j+31h (симв. формат номера).
Если предварительно существующий номер сегмента не был нулевой и включена
опция /N (сохр. файл с заданным именем), то область данных записывается на
диск в файл. Имя файла равно текущему имени программы, с расширением файла -
Vxx, где "xx" две hex-цифры номера сегмента j+31h (симв. формат номера).
+129
View File
@@ -0,0 +1,129 @@
4.3 FORMAT OF LINK COMPATIBLE OBJECT FILES
NOTE
Section 4.3 is interesting
material for users who wish to
know the load format of LINK-80
relocatable object files. Most
users will want to skip this
section, as it does not contain
material neccessary to the
operation of the package.
LINK-compatible object files consist of a bit stream. Individual fields
within the bit stream are not aligned on byte boundaries, except as
noted below. Use of a bit stream for relocatable object files keeps the
size of object files to a minimum, thereby decreasing the number of
disk reads/writes.
There are two basic types of load items: Absolute and Relocatable. The
first bit of an item indicates one of these two types. If the first bit
is a 0, the following 8 bits are loaded as an absolute byte. If the
first bit is a 1, the next 2 bits are used to indicate one of four
types of relocatable items:
00 Special LINK item (see below).
01 Program Relative. Load the following 16 bits
after adding the current Program base.
10 Data Relative. Load the following 16 bits after
adding the current Data base.
11 Common relative. Load the following 16 bits
after adding the current Common base.
Special LINK items consist of the bit stream 100 followed by:
a four-bit control field
an optional A field consisting of a two-bit address type
that is the same as the two-bit field above except 00
specifies absolute address
an optional B field consisting of 3 bits that give a
symbol length and up to 8 bits for each character of the
symbol
A general representation of a special LINK item is:
1 00 xxxx yy nn zzz + characters of symbol name
-------- ---------------------------------
A field B field
xxxx Fout-bit control field (0-15 below)
yy Two-bit address type field
nn Sixteen-bit value
zzz Three-bit symbol length field
The following special types have a B-field only:
0 Entry symbol (name for search)
1 Select COMMON block
2 Program name
3 Request library search
4 Extension LINK items (see below)
The following special LINK items have both an A field and a B field:
5 Define COMMON size
6 Chain external (A is head of address chain, B is name of
external symbol)
7 Define entry point (A is address, B is name)
The following special LINK items have an A field only:
8 External - offset. Used for JMP and CALL to externals
9 External + offset. The A value will be added to the two
bytes starting at the current location counter
immediately before execution.
10 Define size of Data area (A is size)
11 Set loading location counter to A
12 Chain address. A is head of chain, replace all entries
in chain with current location counter. The last entry
in the chain has an address field of absolute zero
13 Define program size (A is size)
14 End program (forces to byte boundary)
The following special LINK item has neither an A nor a B field:
15 End file
An Extension LINK item follows the general format of a B-field-only
special LINK item, but contents of the B-field are not a symbol name.
Instead, the symbol area contains one character to identify the type of
Extension LINK item, followed by from 1 to 7 characters of additional
information.
Thus, every Extension LINK item has the format:
1 00 0100 zzz i jjjjjjjj
where
zzz may be any three bit integer (with 000 representing 8),
i is an eight bit Extension LINK item type indentifier,
and
jjjjjjjj are zzz-1 eight bit character of information whose
significance depends on i
At present, there is only one Extension LINK item:
i = X'35' COBOL overlay segment sentinel
zzz = 010 (binary)
j = COBOL segment number -49 (decimal)
When the overlay segment sentinal is encountered by the linker,
the current overlay segment number is set to the value of j+49. If
the previously existing segment number was non-zero and a /N
switch is in effect, the data area is written to disk in a file
whose name is the current program name and whose extension is Vnn,
where nn are the two hexadecimal digits representing the number
j+49 (decimal).