c71e249a4e
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>
969 lines
66 KiB
Plaintext
969 lines
66 KiB
Plaintext
Компилятор языка Си в системе 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
|