PDA

Просмотр полной версии : Вызов функций через RST



axor
15.11.2005, 12:48
Уважаемые спектрумисты!

Посмотрите нижепредложенный вариант вызова каких-либо функций через RST без использования регистров.

Задачи две.
1. Как можно уменьшить длину?
2. Как можно сделать быстрее?

Ваши предложения, комментарии.



;Вызов функций через RST, без использования регистров.

ORG #6000

LD HL,#FFFF ;Параметр
LD BC,#FEFE ;Параметр
LD DE,#0101 ;Параметр
LD A,#11 ;Параметр
CALL EMUL_RST ;Вызов функции
DB 0 ;Номер функции (0-255)
DB 1 ;Номер подфункции (0-127)

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

EMUL_RST ;такт, байт
LD (BACKBC+1),BC ;20, 4
EX (SP),HL ;19, 1
LD B,(HL) ;НОМЕР ФУНКЦИИ ;7, 1
INC HL ;6, 1
LD C,(HL) ;НОМЕР ПОДФУНКЦИИ ;7, 1
INC HL ;6, 1
EX (SP),HL ;19, 1
PUSH HL ;11, 1
LD L,B ;4, 1
LD H,RST_TAB/256 ;7, 2
LD B,(HL) ;7, 1
INC H ;4, 1
LD H,(HL) ;7, 1
LD L,B ;4, 1
LD B,0 ;7, 2
SLA C ;8, 2
ADD HL,BC ;11, 1
LD B,(HL) ;7, 1
INC HL ;6, 1
LD H,(HL) ;7, 1
LD L,B ;4, 1
EX (SP),HL ;19, 1
BACKBC LD BC,0 ;10, 3
RET ;10, 1

;----------------------------
;Итого: 217 тактов, 32 байта.
;----------------------------

;Таблица переходов на функции.
;Лежит только с ровного адреса!
;Занимает 512 байт на 256 функций.

ORG #7000
RST_TAB DB FUNCTION0 ;младший байт адреса функции
ORG #7100
DB FUNCTION0 ;старший байт адреса функции

;Сами функции...
FUNCTION0
DW F0_1 ;адрес подфункции
DW F0_2 ;адрес подфункции

;Подфункция 1
F0_1 RET

;Подфункция 2
F0_2 RET


В приложении тот же пример.

acidrain
16.11.2005, 11:19
Хммм, а если не секрет, то для чего это планируешь применить?

axor
16.11.2005, 18:07
Хммм, а если не секрет, то для чего это планируешь применить?

Для любой прошивки ПЗУ, функциями которой можно будет пользоваться не меняя номеров устоявшихся функций от версии к версии.

Spectre
17.11.2005, 15:21
Написано оптимально, короче сделать не получается.

jtn
17.11.2005, 16:12
не расточительство ли тратить в 16к пзу 512б на какую то таблицу?

fk0
17.11.2005, 16:30
Посмотрите нижепредложенный вариант вызова каких-либо функций через RST без использования регистров.
Задачи две.
1. Как можно уменьшить длину?
2. Как можно сделать быстрее?




ld de, args
ld bc, args
call function
.....

function:
JP xxxx ; где xxxx патчится на нужный адрес
; при загрузке программы в память.

fk0
17.11.2005, 16:32
Для любой прошивки ПЗУ, функциями которой можно будет пользоваться не меняя номеров устоявшихся функций от версии к версии.

Вот в инструкции JP xxxx номер вместо xxxx и записывается. Это когда
на диске. А когда в памяти номер меняется на адрес. Отдельно имеется
массив адресов -- индекс в массиве этот самый номер.

fk0
17.11.2005, 16:33
Отдельно имеется
массив адресов -- индекс в массиве этот самый номер.

Это уже в ПЗУ.

fk0
17.11.2005, 16:33
не расточительство ли тратить в 16к пзу 512б на какую то таблицу?

Нет. Каждая функция из той таблицы по-более займёт.

SAM style
17.11.2005, 17:19
Написано оптимально, короче сделать не получается.
Неужто?


ex (sp),hl
ld a,(hl)
inc hl
ex af,af'
ld a,(hl)
inc hl
ex af,af'
ex (sp),hl
push hl
ld l,a
ld h,RST_TAB_hi_addr
ld a,(hl)
inc h
ld h,(hl)
ld l,a
ex af,af'
add a,a
add a,l
ld l,a
ld a,h
adc a,0
ld h,a
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
ex (sp),hl
ret

Сейчас уже не помню точно, но что-то около 180-190 тактов.

Sinus
17.11.2005, 18:30
SAM style: у тебя регистру A сипец наступает (внимательно не смотрел, но по ходу и HL тоже) ^_~

axor: если тебе не только теоретически интересно как сделать короче и быстрее, то вариант предложенный fk0 самый найлучший. разделение функции на две (функция / подфункция) это неправильный подход принесённый со времён DOS на PC.

SAM style
17.11.2005, 18:47
SAM style: у тебя регистру A сипец наступает (внимательно не смотрел, но по ходу и HL тоже) ^_~
HL в неприкосновенности. Насчет A - можно окаймить прогу этим:



LD (a_reg+1),a
...
a_reg LD a,0
RET


+17 тактов и 5 байт (все равно быстрее). Убьется тоько A'
От подфункций действительно надо избавиться - 256 штук одних функций это не так уж и мало, и то вряд ли фантазии хватит их все забить.

jtn
17.11.2005, 18:51
HL в неприкосновенности. Насчет A - можно окаймить прогу этим:
ага. в ПЗУ-то..

SAM style
17.11.2005, 19:50
ага. в ПЗУ-то..
Мдя... Не учел.
Тогды A приносим в жертву. На крайний случай для передачи параметров у нас есть еще 6 альтернативных регистров, IX и IY. Можно даже R задействовать, но это уже изврат...
Можно еще PUSH-ей и POP-ов напихать, но выигрыша или не будет, или он будет мизерным.

axor
18.11.2005, 09:17
Вот в инструкции JP xxxx номер вместо xxxx и записывается. Это когда
на диске. А когда в памяти номер меняется на адрес. Отдельно имеется
массив адресов -- индекс в массиве этот самый номер.

А почему нельзя сделать JP xxxx уже в ПЗУ, чтобы не патчить программу? Ведь если сделать их в самой программе, которая вызывает эти функции, то для этого нужно будет опять же создавать и хранить таблицы этих JP xxxx, а потом патчить. А если сразу разместить JP xxxx в ПЗУ, то для этого понадобится на байт больше, чем обычно.

Хотя кто его знает, какую память нужно сразу экономить ПЗУ или ОЗУ...

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

fk0
18.11.2005, 11:45
SAM style: у тебя регистру A сипец наступает (внимательно не смотрел, но по ходу и HL тоже) ^_~


У меня тоже наступает. HL и A. Просто нужно иметь соглашение,
что через эти регистры ничего не передаётся при вызове нелокальных
функций. Через HL может только адрес объекта и/или таблицы
виртуальных функций передаваться. Через A -- номер функции.



axor: если тебе не только теоретически интересно как сделать короче и быстрее, то вариант предложенный fk0 самый найлучший. разделение функции на две (функция / подфункция) это неправильный подход принесённый со времён DOS на PC.

Насколько я знаю, это подход принесённый (и в дос в т.ч.)
ещё из CP/M. (ld c, функция; call #0005). Почему -- понятно.
Загруженный код программы может без всяких хитростей
быть сразу запущен.

В современных операционных системах тоже так. Встроенные функции ОС (например linux или bsd -- как там в windows NT понять сложно, но вроде как тоже примерно так) вызываются аналогичным образом. Например, функции open или read (к языку C прилагаются функии-обёртки распределяющие аргументы по разным регистрам процессора и вызывающие программное прерывание).

Встроенных функций ОС очень мало. У того же линуха порядка
200-300 штук. Другое дело -- библиотечные функции. Их много. Они могут быть загружены или могут не быть загружены. В современных ОС используется подход близкий к указанному мною. Любая функция доступна по её адресу. Но перед тем как какая функция может быть
использована требуется загрузка библиотеки и настройка программы и библиотеки на совместное использование.

fk0
18.11.2005, 11:54
Мдя... Не учел.
Тогды A приносим в жертву. На крайний случай для передачи параметров у нас есть еще 6 альтернативных регистров, IX и IY. Можно даже R задействовать, но это уже изврат...
Можно еще PUSH-ей и POP-ов напихать, но выигрыша или не будет, или он будет мизерным.

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

SAM style
18.11.2005, 12:09
У меня тоже наступает. HL и A

ex (sp),hl на стеке - парам.HL, в HL - адрес следующий за call
... берем A и A' - номер функции, подфункции
ex (sp),hl на стеке - адрес возврата (+2), парам.HL - в HL
push hl стек: парам.HL, адрес возврата
... в HL вычисляем адрес процедурины
ex (sp),hl стек: адрес процедуры, адрес возврата, парам.HL - в HL
ret переход на адрес процедуры.

Где порча HL? Убей, не вижу.

fk0
18.11.2005, 12:10
А почему нельзя сделать JP xxxx уже в ПЗУ, чтобы не патчить программу?


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




Ведь если сделать их в самой программе, которая вызывает эти функции, то для этого нужно будет опять же создавать и хранить таблицы этих JP xxxx,


Таблицы этих JP создаются очень просто и один раз в текстовом редакторе. Каждой "внешней" функции присваивается номер. Делает это автор тоже "внешней" библиотеки, программы или прошивки ПЗУ.
Для использования другими людьми он выпускает специальный файлик для включения в ассемблер:


имя_функции_1:
jp номер_функции_1
имя_функции_2:
jp номер_функции_2
...
имя_функции_N:
jp номер_функции_N

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

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



а потом патчить. А если сразу разместить JP xxxx в ПЗУ, то для этого понадобится на байт больше, чем обычно.


Размещать JP xxx в ПЗУ смысл есть. По фиксированному адресу,
разумеется. Тогда мы тоже дело имеем с номерами и одновременно
с адресами функций. Для ПЗУ такой метод годится. Для ОЗУ, где
библиотека каждый раз, в зависимости от наличия свободной памяти,
может загружаться в разные адреса -- нет, такой метод не приходен.

Надо сказать, размещение массива JP xxxx в ОЗУ имеет ещё одно преимущество. Возможность включения функций-фильтров на входе и выходе системных (библиотечных) функций (путём замены xxxx на собственную функцию-обёртку) Для отладки и т.п. может быть полезно. Например, можно запротоколировать вызовы всех системных функций.



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

И ещё одно. Если массив JP xxxx размещается в ОЗУ это даёт
то преимущество, что размер массива определяется только
количеством вызываемых функций, а не количеством
функций всего:


ifused function
function:
jp function_number
endif
...


С другой стороны, в ПЗУ в любом случае должны
размещаться адреса всех функций. То-есть в варианте
массив адресов функций (2*N) плюс массив JP xxx в ОЗУ (3*m)
против массива JP xxxx в ПЗУ (3*N) выигрывает размещение
массива JP xxxx в ПЗУ (3*N) при m>N/3 , где N -- общее число функций, а m -- число вызываемых функций (используемых
загруженной программой).

Vladimir Kladov
18.11.2005, 15:14
Обсуждался уже очень простой способ, как раз с номерами. Время тратится только во время первого вызова на патч, при следующих вызовах происходит call. Дополнительное примущество: можно использовать call cond. JP использовать нельзя, код в ПЗУ пропатчит не то.
Например, в ПЗУ по адресам 100Н...1FFH находится переходник следующего вида.
ORG 100H
DEFB (E8H)18H,(18H)0

(как вариант, может быть 255 раз RST n на нужный адрес, только тогда надо из стека вытолкнуть один лишний адрес возврата в коде патчилки).

После чего с адреса 200H в ПЗУ пишется примерно такой код:
ORG 200H
EX (SP),IX:PUSH HL,DE,AF
LD E,(IX-2):LD D,0:LD HL,TableJumps:ADD HL,DE:ADD HL,DE
LD (IX-2),L:LD (IX-1),H
;здесь можно приготовить код для Jumper'а:
LD A,C3h:LD (Jumper),A ; а можно и не готовить, если он 1 раз готовится, и есть уверенность, что его никто не будет портить
;а эту часть Jumper'а надо установить сейчас в адрес перехода:
LD (Jumper+1),HL
POP AF:POP DE:POP HL
EX (SP),IX:JP Jumper

Место для Jumper должно быть выделено где-то в ОЗУ, 3 байта, например:
ORG FFF0h
Jumper: JP 0

Да, номера здесь должны быть в диапазоне 100H..1FFH, чтобы 0-е адреса не занимать, как раз для RST и прочего.

В ассемблере пишется
Fun0 EQU 100H
Fun1 EQU 101H
...

От версии прошивки ПЗУ ничего не зависит, программа всегда попадает куда надо. Только первый вызов длинный, прочие всегда прямой call.

Другой (лучший!) вариант, самый распространенный для микрокомпьютеров - это выделить в ПЗУ таблицу переходов, по 3 байта каждый, с заданного адреса, например, опять со 100H:
ORG 100H
Fun0: JP Fun0_implement
Fun1: JP Fun1_implement
...
В асме использовать таблицу EQU:
Fun0 EQU 100H
Fun1 EQU 103H
...
Никаких извратов, вместо номеров 0..FF используются адреса 100H..3FDH с шагом 3. Все быстро, можно использовать JP. Расходы в ПЗУ еще меньше чем в случае 1 выше. Я бы выбрал именно вариант 2.

Sinus
18.11.2005, 20:58
короче поехали. отвечаю всем подряд.

axor
я так понял ты новое ПЗУ собираешься делать, причём такое, что б всё было круто независимо от версии прошивки.

наиболее компромиссный вариант- создать в ПЗУ табличку переходов вида

func1 jp real-func-1
func2 jp real-func-2
....

на самом деле нужно думать, что важнее, память (когда используем код вида

....
RST #10
DB func_number
....

) или быстродействие (табличка переходов). я, так же как и Vladimir Kladov считаю что jp-табличка лучше.

fk0
в современных осях на современных пэцэтах есть современный protected mode, в котором можно переопределить адреса переходов комманд int 0x00 ... int 0xFF.

чтобы код программы мог быть запущен, необязательно передавать параметры через стек, патчить прогу или использовать конструкции вида RST XX: DB YY

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


с адреса #0000 находится 256 jp XXXX. это системные вызовы. параметры передаются в регистрах. если нужно передать адрес, то он всегда в HL, если нужно передать один байт, то он всегда в A.

ну и т.д.

далее всё зашибенно работает.

captain cobalt
18.11.2005, 21:49
call function
.....

function:
JP xxxx ; где xxxx патчится на нужный адрес
; при загрузке программы в память. Предлагается патчить адрес в команде CALL.
Тогда вызов будет совершаться напрямую, а JP не нужен.

для этого нужно будет опять же создавать и хранить таблицы этих JP xxxx, а потом патчить.Когда пропатчивание завершено, таблицы больше не нужны. Память можно использовать для других целей.

В том-то и суть, что этот код обязан быть в программе. Именно для того чтобы патчить. Ничто не мешает сделать его частью ПЗУ. Тогда он будет существовать в единственном экземпляре и патчить все другие программы.

номер функции не изменяется. Вот его уже менять нельзя. Также предлагается использовать не номера функций, а осмысленные символьные имена.

Vitamin
18.11.2005, 22:20
Зачем все усложнять?
Есть 8 рестартов, из них можно использовать 5-6 для своих нужд- разделение по функциональности. И 256 функций для каждого рестарта. Итого код:

ld hl,...
ld de,...
ld bc,...
ld a,...
rst N
db func

N: ex (sp),hl ;(sp) - param, hl- retaddr-1
exa
ld a,(hl)
inc hl
ex (sp),hl ;(sp) - retaddr hl- param
push hl
ld l,a
ld h,'table
ld a,(hl)
inc h
ld h,(hl)
ld l,a
exa
ex (sp),hl ;(sp)- jump hl- param
ret

ЗЫ: комбинацию
add a,l
ld l,a
ld a,h
adc a,0
ld h,a

можно заменить на
add a,l
ld l,a
adc a,h
sub l
ld h,a

acidrain
19.11.2005, 13:12
Для любой прошивки ПЗУ, функциями которой можно будет пользоваться не меняя номеров устоявшихся функций от версии к версии.
Эт я понял, а прошивка какая? 48бейсик? =) Или что свое?

axor
19.11.2005, 15:48
Эт я понял, а прошивка какая? 48бейсик? =) Или что свое?

Секрет...

axor
19.11.2005, 15:57
Есть 8 рестартов, из них можно использовать 5-6 для своих нужд- разделение по функциональности. И 256 функций для каждого рестарта.

Тогда получается, что для каждого рестарта нужна своя таблица адресов функций, а это, как мне кажется, расточительно.

Aleksey Senilov (500:8332/1)
20.11.2005, 08:42
Привет тебе, _/Alexandr/_!

17 ноября 2005 22:58, Alexandr Sinyakov писал(а) All:


Тогды A приносим в жертву. Hа крайний случай для передачи параметров у
нас есть еще 6 альтернативных регистров, IX и IY. Можно даже R
задействовать, но это уже изврат... Можно еще PUSH-ей и POP-ов
напихать, но выигрыша или не будет, или он будет мизерным.

Если А в жертву, то по-моему гораздо лучше в нем и передавать номер функции! Потеря в размере клиентского кода, но немалый выйгрыш в скорости.

;Вариант с номером после команды вызова и с порчей А
ex (sp), hl ;19
ld a, (hl) ;7
inc hl ;6
ex (sp), hl ;19
push hl ;11
ld h, rsttab/256;7
ld l, a ;4
ld a, (hl) ;7
inc h ;4
ld h, (hl) ;7
ld l, a ;4
ex (sp), hl ;19
ret ;10
итого 124 такта

;Вариант с передачей номера в А
push hl ;11
ld h, rsttab/256;7
ld l, a ;4
ld a, (hl) ;7
inc h ;4
ld h, (hl) ;7
ld l, a ;4
ex (sp), hl ;19
ret ;10
;Итого 73 такта

Более того, можно использовать оба варианта на одном и том же коде! Второй вариант, это ведь просто другая точка входа от первого варианта. :)
А разбор подфункции логичнее сделать прямо в тех функциях, где это надо. Hо не выносить в общий механизм вызова.

Если сравнивать вызов функции по номеру и по таблице JP, у каждого варианта есть и достоинства и недостатки.

До новых встреч! С уважением, Тхэнн.
... Nereal was created for us

GriV
20.11.2005, 09:22
то указанные способы подразумевают "взаимодействие" программы загруженной и парсера, работающего в ПЗУ. посмотрите на http://zxdocs.fatal.ru/coding/module.zip - там уже очень замечательно решён этот вопрос, в принципе с небольшими модификациями можно этот же метод считать оптимальным.

Вообще, моё глубокое убеждение состоит в том, что глупо реализовывать парсинг вызова в ходе работы самой программы - при загрузке программы всё равно тратится ОЧЕНЬ много процессорного времени и препроцессинг тела программы незначительно увеличит это время (загрузка одного сектора в 256 байт длится около 20-30 тысяч тактов, а среднестатистическая программа-утилита занимает секторов 5-6, игрушки и демки в разы больше; процесс настройки прямых вызовов рутин из ПЗУ жрёт качественно меньше тактов - скорей всего уложится в одно прерывание), однако отсутствие парсера в процессе ВЫПОЛНЕНИЯ программы приведёт просто к качественному ускорению РАБОТЫ кода.

Например, взять программы умножения двух однобайтовых чисел. Сама программа занимает около 500 тактов. А парсер 200. Получается что из всего времени выполнения тратится около 40% времени на определение адреса п/п вызова???? И это каждый вызов!!! Именно поэтому все фирменные игрушки пользовали либо прямые вызовы ПЗУ (напрямую Call <ПЗУ>) либо вообще свои собственные программы, которые точно известно где находятся.

GriV
20.11.2005, 09:36
эффективно (в смысле затрат памяти) - использовать символьные имена для работы с п/п ПЗУ :D (я бы даже более сказал :D) однако имеет право на жизнь хотя бы потому, что такие функции никогда не перекроют друг друга.
Например:

call PrintChar

А если взять безликий

RST 16
defb 81
defb 12

то тому, кто будет писать такую п/п будет необходимо будет держать в руках справочник чтобы смотреть соответствие между вызываемой функцией и её байтами описателями (те самые defb) - такую работу должен выполнять не программист, а машина.
Да и запомнить символьное имя PrintChar в любом случае проще.
Кроме того, символьные имена хорошо жмутся (5 бит используют из 8, если даже взять спецсимволы то 6 бит из 8, или скажем обработать хаффманом), так что трёхсимвольное имя будет кушать практически столько же места сколько вызов через DefB.

P.S. Под перекрытием я подразумеваю возможность вызова разных функций при том же синтаксисе - PrintChar либо не оттранслируется вообще, потому что нет такой функции, либо будет транслироваться замечательно. Rst Defb Defb при смене набора функций придётся отслеживать. Первый Defb отвечает за группу функций - если после обновления версии случилось так что группа расширилась за 256 значений функций, то придётся ещё один номер группы резервировать и это в свою очередь головняк и головняк

Vladimir Kladov
20.11.2005, 10:27
Есть такая штука называется макрокоманда. Определяется макрокоманда

PrintChar MACRO char
RST 16:DEFB char
ENDM

И дальше в коде пишется

PrintChar 'a'

Глазами по таблицам лазить не нужно. Для того и сущствуют компиляторы.

Vitamin
20.11.2005, 12:10
Тогда получается, что для каждого рестарта нужна своя таблица адресов функций, а это, как мне кажется, расточительно.
не более расточительно, чем хранить таблицы переходов для каждой подфункции. зато геморроя меньше.

axor
22.11.2005, 19:51
не более расточительно, чем хранить таблицы переходов для каждой подфункции. зато геморроя меньше.

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

SAM style
22.11.2005, 21:01
А тут никто не подумал, что будет если пытливый мозг вроде моего попытается вызвать подфункцию с номером, превышающим число подфункций у данной функции? В лучшем случае будет вызов совсем другой проги, а в худшем - ступор компа. Либо защиту от такого мараZьма ставить надо, либо избавляться от подфункций.

CHRV
22.11.2005, 23:38
RST использовались как функции в Спринтере 2000 в его ОСИ Estex. Можно сказать достаточно удобно с точки зрения программирования. Хотя никто не мешает сделать керналь как в CP/M (таблицу вызовов).

axor
24.11.2005, 10:39
А тут никто не подумал, что будет если пытливый мозг вроде моего попытается вызвать подфункцию с номером, превышающим число подфункций у данной функции? В лучшем случае будет вызов совсем другой проги, а в худшем - ступор компа. Либо защиту от такого мараZьма ставить надо, либо избавляться от подфункций.

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

fk0
24.11.2005, 13:47
ex (sp),hl стек: адрес процедуры, адрес возврата, парам.HL - в HL
ret переход на адрес процедуры.

Где порча HL? Убей, не вижу.


Без этих ex (sp), hl получается существенно быстрей.

fk0
24.11.2005, 14:14
fk0
в современных осях на современных пэцэтах есть современный protected mode, в котором можно переопределить адреса переходов комманд int 0x00 ... int 0xFF.


И на "несовременных пэцэтах" тоже можно. Суть не в том.
Суть в том, что номера int жёстко закреплены за функциями.
Ибо без какого-либо минимального набора вообще никак.



чтобы код программы мог быть запущен, необязательно передавать параметры через стек, патчить прогу или использовать конструкции вида RST XX: DB YY
надо всего лишь прийти к какому- либо соглашению о передаче параметров и вызовах функций. допустим


Да. Но это соглашение должно быть. Вот именно в этом суть.



с адреса #0000 находится 256 jp XXXX. это системные вызовы. параметры передаются в регистрах. если нужно передать адрес, то он всегда в HL, если нужно передать один байт, то он всегда в A.


Это фактически мой вариант. Просто таблица переходов размещается в ПЗУ и патчить её не надо, потому как само
ПЗУ уж знает где его функции находятся.

Что касается аргументов я предпочитаю следовать соглашениям
принятым для компилятора HiTech-C. Они достаточно удобны.
Аргументы передаются в DE, BC и далее в стеке. Результат
возвращается в HL. Иногда я отступаю от таких соглашений,
когда передача через HL или A более эффективна. Но такие
функции обычно "внутренние" для программы, наружу не
экспортируются, поэтому в их отношении допустимо всё что
угодно.

Регистр HL и A имеют специальное значение. При программировании
в OO-стиле в HL содержится указатель на структуру-объект,
первым элементом которой является адрес массива "JP xxx" указателей виртуальных функций. При вызове виртуальной функции
регистр A используется под её номер (до 85 функций). Схема
примерно такая:



; В коде программы:
...
ld hl, object_or_inherited_object
call virtual_function
...
call non_virtual_function
...

; В коде включаемого "*.h"-файла
virtual_function:
ld a, function_number
jp call_virtual
...
; таблица функций -- патчится после загрузки:
; номера функций заменяются их адресами извлечёнными
; из таблицы функций библиотеки (располагается в файле библиотеки)
non_virtual_function:
jp function_number
non_virtual_function2:
jp function_number2
...


; В коде файла поддержки виртуальных функций:
call_virtual:
; здесь A складывается с (HL) и извлекается адрес
; из таблицы виртуальных функций
; присутствующей в файле библиотеки
....
ex (sp), hl
ret ; ~120 тактов


;---------------------------------------------------------------------

; В коде файла библиотеки
; ВАЖНО: компилируется НЕЗАВИСИМО от файла основной программы
; и динамически подгружается "на лету"
virtual_function:
.... ; обычный код
ret

non_virtual_function:
.... ; обычный код
ret




Множественное наследование не предусмотрено, ибо сложно
получается.



далее всё зашибенно работает.

Да. Был бы на это ЕДИНЫЙ СТАНДАРТ...

Sinus
24.11.2005, 15:08
Что касается аргументов я предпочитаю следовать соглашениям принятым для компилятора HiTech-C. Они достаточно удобны.
{{skip}}
Регистр HL и A имеют специальное значение. При программировании
в OO-стиле в HL содержится указатель на структуру-объект,

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


Множественное наследование не предусмотрено, ибо сложно
получается.
оно нужно так же как и goto (т.е. ненужно почти никогда)


Да. Был бы на это ЕДИНЫЙ СТАНДАРТ...
о! да...
но его нет ^_~

Vitamin
24.11.2005, 20:30
При программировании
в OO-стиле в HL содержится указатель на структуру-объект,
первым элементом которой является адрес массива "JP xxx" указателей виртуальных функций.
а почему бы не использовать для этих целей индексный регистр? имхо это более рационально (все-таки структура). а вторую регистровую пару использовать для хранения адреса переменных на стеке. получается довольно неплохо

fk0
25.11.2005, 10:11
это если объектами рулить.
как показывает практика, ООП эффективно только на достаточно больших задачах.
в случае спектрума зачастую обычный процедурный подход оказывается эффективнее.


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



оно нужно так же как и goto (т.е. ненужно почти никогда)


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

А если идти и дальше
этим путём, то любые операторы ветвления и циклов тоже
не нужны. Ибо они реально, без почти, не нужны. Программа
может быть элементарно преобразована в эквиэвалентную,
использующую исключительно функции и оператор '?' (в языке C).

Глупость про ненужность goto -- это типичный "слышал звон,
но совершенно не в курсе дела о чём вообще речь". Если он такой
ненужный, чего ж он мало того, что в последних версиях стандартов
остался, так ещё и обрастает разными расширениями? То-есть
да, он таки не нужен, ровно в той степени, как ненужны while и for.

fk0
25.11.2005, 10:15
...объектами рулить...

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

Можно было бы. Есть одно но: если размер косвенно адресуемой
структуры мал, если доступ к ней по большей части последовательный,
если часто используется адресная арифметика -- в таких случаях
через HL быстрей. Ибо на каждый байт доступный через индексный
регистр уходит лишних 8 тактов и лишние 2 байта. Если у тебя
большая структура со случайным доступом -- да, индексный регистр
лучше. Но HL и IY переложить не долго.

Sinus
25.11.2005, 11:03
А если идти и дальше
этим путём, то любые операторы ветвления и циклов тоже
не нужны. Ибо они реально, без почти, не нужны. Программа
может быть элементарно преобразована в эквиэвалентную,
использующую исключительно функции и оператор '?' (в языке C).
'?' - это и есть вариант оператора ветвления.


Глупость про ненужность goto -- это типичный "слышал звон,
но совершенно не в курсе дела о чём вообще речь". Если он такой
ненужный, чего ж он мало того, что в последних версиях стандартов
остался, так ещё и обрастает разными расширениями? То-есть
да, он таки не нужен, ровно в той степени, как ненужны while и for.
сколько раз за последние лет 5 ты использовал goto в своих программах?
лично я - 0 (ноль).
есть break, есть continue.

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

goto нужен зачем? затем, что бы в каком-то исключительном случае перепрыгнуть через несколько блоков.
ну так в таком случае есть Exceptions.

и вообще, грамотней всего поступили в java- там goto есть, но это вариация оператора break, только указывается куда надо этот break делать.

--------

как известно, любую программу можно написать без goto.

так же как и с goto, зато без while, for.
можно не использовать функций.
можно обойтись одними goto и тернарными операторами.

всё зависит от степени маразматичности писавшего эту программу.

однако существуют некие общепризнанные нормы, выверенные годами, как надо писать программы, а именно: использовать функции, for, while, и свести использование goto к минимуму.

GriV
26.11.2005, 08:44
Есть такая штука называется макрокоманда. Определяется макрокоманда

PrintChar MACRO char
RST 16:DEFB char
ENDM

И дальше в коде пишется

PrintChar 'a'

Глазами по таблицам лазить не нужно. Для того и сущствуют компиляторы.

Проблема парсинга от этого не пропадёт, как была так и останется.

Впрочем не у всех Z80 на 3,5 МГц видимо :D у кого то он на 2ГГц :D

GriV
26.11.2005, 08:52
'?' - это и есть вариант оператора ветвления.

сколько раз за последние лет 5 ты использовал goto в своих программах?
лично я - 0 (ноль).
есть break, есть continue.

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

goto нужен зачем? затем, что бы в каком-то исключительном случае перепрыгнуть через несколько блоков.
ну так в таком случае есть Exceptions.

и вообще, грамотней всего поступили в java- там goto есть, но это вариация оператора break, только указывается куда надо этот break делать.

--------

как известно, любую программу можно написать без goto.

так же как и с goto, зато без while, for.
можно не использовать функций.
можно обойтись одними goto и тернарными операторами.

всё зависит от степени маразматичности писавшего эту программу.

однако существуют некие общепризнанные нормы, выверенные годами, как надо писать программы, а именно: использовать функции, for, while, и свести использование goto к минимуму.

Кем принятые для чего приняты?

Я все программы на ZX-BASIC писал только с goto и попробуй их перепиши без этого оператора, потом ты в ней запутаешься.

ZX-BASIC вообще не имеет repeat intil () или while (), только for next, а как делать циклы с неопределённым заранее количествои итераций?

Это просто мастодонты первых выпусков по специальности "программист" такую имели привычку и повсеместно внедряли.

Аргументы:

1) с GOTO программы плохо читаются - так же как и без GOTO, давайте меткам GOTO осмысленные имена или каждый переход GOTO сопровождайте комментарием.

2) GOTO медленно работает - ещё одно заблуждение, на трансляторах всё медленно работает, а в компиляторах всё зависит от адаптивности последних

3) с GOTO программа неустойчива - это как раз проблемы компиляторов, если из цикла скажем FOR NEXT осуществлялся GOTO то тупой компилятор сохранённые на стеке данные цикла не снимал, делал GOTO и в результате фатальный исход программы - вопрос решается сменой компилятора.

Sinus
27.11.2005, 14:45
Кем принятые для чего приняты?
умными дядьками.


Я все программы на ZX-BASIC писал только с goto и попробуй их перепиши без этого оператора, потом ты в ней запутаешься.
блин, сколько раз повторять, до маразма можно довести всё что угодно.
естественно, что на ZX-BASIC без GOTO ну никак, ибо ничего другого там нету.


ZX-BASIC вообще не имеет repeat intil () или while (), только for next, а как делать циклы с неопределённым заранее количествои итераций?
не использовать ZX-BASIC


1) с GOTO программы плохо читаются - так же как и без GOTO, давайте меткам GOTO осмысленные имена или каждый переход GOTO сопровождайте комментарием.
а нафига давать комментавии или осмысленные имена меткам, если гораздо удобнее не использовать GOTO?


2) GOTO медленно работает - ещё одно заблуждение, на трансляторах всё медленно работает, а в компиляторах всё зависит от адаптивности последних
первый раз слышу этот аргумент от тебя, ибо совершенно ясно, что GOTO компилится в простой JP, что быстро.


3) с GOTO программа неустойчива - это как раз проблемы компиляторов, если из цикла скажем FOR NEXT осуществлялся GOTO то тупой компилятор сохранённые на стеке данные цикла не снимал, делал GOTO и в результате фатальный исход программы - вопрос решается сменой компилятора.
ещё один аргумент, придуманный тобой.
никакой компилятор никаких данных на стеке во время цикла не создаёт, а даже если б и создавал, то есть прекрасный оператор break (в нормальных языках), который не прыгает фиг знает куда, а чётко выходит из текущего цикла.

на самом деле умные дядьки приводят только первый аргумент, ибо если программа большая (большая, это с объёмом исходных текстов > 4mb), тогда GOTO ну совсем не рулит. и комментарии, и метки тебе не помогут разобраться через месяц в бесконечных и запутанных GOTO.

acidrain
27.11.2005, 19:19
RST 16
defb 81
defb 12

то тому, кто будет писать такую п/п будет необходимо будет держать в руках справочник чтобы смотреть соответствие между вызываемой функцией и её байтами описателями (те самые defb) - такую работу должен выполнять не программист, а машина.

Предопределить в макросах, как это сделано в AmigaOS:


include OpenLib.i
rst OpenLib
db.w PrintChar


OpenLib = n; номер рст =)
PrintChar = -12 ; смещение от начала либлы

GriV
28.11.2005, 16:46
первый раз слышу этот аргумент от тебя, ибо совершенно ясно, что GOTO компилится в простой JP, что быстро.

Ну это ты так думаешь - возьми любой из компиляторов - достань и посмотри во что там компилится GoTo - думаю ты сильно удивишься :D

При этом даже голый goto (программа с единственным оператором, в которой нет других строк вообще) будет в весьма хитрую конструкцию компилится. Очень хороший компилятор может быть будет так компилить (в jp) да и то это под большим сомнением. Аналогично и про стек и переменные на нём в не только в нём - регистрах и т.д.

GriV
28.11.2005, 16:50
2AcidRian> вопрос не в том что можно макросы назначит а в том, что придётся рассчёт точек перехода во время работы программы делать, а не до её работы как я предлагаю (? неужели это такая непонятная мысль), что ускорит выполнение работы программы.

GriV
28.11.2005, 16:54
2Sinus> очень оригинальный ответ на вопрос "кем и для чего" - "умными дядьками".

Не использовать ZX-BASIC - это что оффтоп???? А меня ещё в писюканстве обвиняли :(

И вообще, касательно GoTo ты сам себе противоречишь - то ты говоришь что GoTo транслируется в просто Jp <адрес>, то ты говоришь что лучше использовать циклы repeat until и do while и т.п., которые компилируются в такие жуткие конструкции, что просто ужасаешься. А тогда (если Goto быстрей) зачем вообще операторы циклов нужны???

Sinus
28.11.2005, 22:46
Ну это ты так думаешь - возьми любой из компиляторов - достань и посмотри во что там компилится GoTo - думаю ты сильно удивишься :D
хорошо, берём любой компилятор (MS Visual C 7).

void main(void) {test: goto test;}

-->

.... {{c_startup_code}} ....
test: jmp test

и чему удивляться? что goto скомпилилось в один jmp?


При этом даже голый goto (программа с единственным оператором, в которой нет других строк вообще) будет в весьма хитрую конструкцию компилится. Очень хороший компилятор может быть будет так компилить (в jp) да и то это под большим сомнением.
ну не знаю что ты за компиляторами пользуешься, но даже те компиляторы которые пишу я (по работе иногда приходится) такой глупостью не занимаются, и компилят всё очевидно.


Аналогично и про стек и переменные на нём в не только в нём - регистрах и т.д.

в общем, как говорится учите матчасть ;)
продизассемблируй разок что сгенерил не совсем древний компилятор, и думаю там есть на что посмотреть.

Sinus
28.11.2005, 22:52
2Sinus> очень оригинальный ответ на вопрос "кем и для чего" - "умными дядьками".
ответ правильный ;)


Не использовать ZX-BASIC - это что оффтоп???? А меня ещё в писюканстве обвиняли :(
??? а что на ZX нет ничего кроме бейсика? :confused: ну всё, пойду топиться. а куда ж подевались C (аж две штуки есть, причём на одном ещё и писать можно), Pascal (ну так, поприкалываться), MegaBasic (там в wlile, и repeat и until), BetaBasic (т.е.) и толпа ассемблеров???


И вообще, касательно GoTo ты сам себе противоречишь - то ты говоришь что GoTo транслируется в просто Jp <адрес>, то ты говоришь что лучше использовать циклы repeat until и do while и т.п., которые компилируются в такие жуткие конструкции, что просто ужасаешься. А тогда (если Goto быстрей) зачем вообще операторы циклов нужны???
я себе не противоречу.
да, в плане производительности выгоднее использовать Goto и писать на ассемблере. а в плане быстроты разработки, я лучше на перловке за 5 минут прогу напишу, чем на асме неделю колупать буду. и пофигу, что перловочный вариант вообще интерпретируется, дико неоптимален да ещё и тормозит- зато я остаток недели буду им пользоваться, а не колупать себе асмом мозг.

GriV
28.11.2005, 22:56
я про спекк вообще то грю...
Там где есть огромные вычислительные мощности и где платформа развивалась на больших деньгах в течение десяти двадцати лет глупо сранивать с плафтормой которая последний коммерческий проект имела лет 10-15 назад. К тому же я согласен с очень высокой оценкой экспертов об оптимизирующем компиляторе MSCPP, но это оффтоп. А вот ты возьми например Blast! - комилятор для ZX-BASIC и сделай это в нём, увидишь что будет именно так.

И ещё раз напиши кроме чистого GoTo набор команд циклов - увидишь что у тебя и стек в дело пойдёт и память полетит в расход.

fk0
29.11.2005, 12:02
'?' - это и есть вариант оператора ветвления.


Это не тот оператор. Это скорей фунция, вычисляющая
одно либо другое выражение (подчёркиваю -- только
выражение, ничего более) в зависимости от значения
лог. условия. Это не оператор, как if.

Другое дело, что и этот оператор может быть заменён
другими логическими выражениями || и &&.



сколько раз за последние лет 5 ты использовал goto в своих программах?


$ grep -r goto . | wc -l
12

Где-то за года полтора.



лично я - 0 (ноль).
есть break, есть continue.


1. завуавалированный goto хуже настоящего.

2. while () {
while () {
for (;;) {
switch() {
...
break ?
}}}}

3. А как относиться к перловым next <метка>?



во всех остальных случаях goto вредно и использовать его не стоит.


"Заставь дурака богу молиться..." (C)



перепрыгнуть через несколько блоков.
ну так в таком случае есть Exceptions.


В чистом C, вокруг longjmp. На 8-битной платформе.
Такты считать не будем. Так ведь тоже вредно, между прочим,
и грабельки подложены аккуратненько где надо.



и вообще, грамотней всего поступили в java- там goto есть, но это вариация оператора break, только указывается куда надо этот break делать.


Так ведь goto вреден? А раз в Java (это, несомненно очень модно) есть -- значит уже не вреден настолько, чтоб им
пренебрегать?

А более взрывоопасно сделано в bash: break <число>. Теперь
попробуй правильно сосчитать скобочки...



всё зависит от степени маразматичности писавшего эту программу.


Вот это действительно верная мысль.



однако существуют некие общепризнанные нормы, выверенные годами, как надо писать программы, а именно: использовать функции, for, while, и свести использование goto к минимуму.

Да, да. И чтобы отступ от <Tab> именно на 4 пробела. А то
работать не будет. С верой в goto, общепризнанные нормы, в
богоугодную ширину табуляции и прочий бред -- это вам в церковь...
Абсолютной истины нет. И спорить на эту тему мне -- только время
терять.

fk0
29.11.2005, 12:08
на самом деле умные дядьки приводят только первый аргумент, ибо если программа большая (большая, это с объёмом исходных текстов > 4mb), тогда


Тогда ту программу, где goto по всем 4-м мегабайтам main() размазан,
точно никакой компилятор не переварит...

fk0
29.11.2005, 12:17
ответ правильный ;)


"Умный дядька" -- это, несомненно, очень авторитетный источник.

Повторюсь: в первоисточнике вопроса о goto утверждалось вовсе
не то, что так любят повторять. На днях буквально натыкался в очередной раз. Просто некоторые граждане по-диагонали читают
и воспринимать всё способны исключительно прямолинейно и
буквально.

Я не спорю, студенты изучающие паскаль склонны с помощью
goto к созданию "спагетти-кода". Но это значит только то что
это значит. Из этого не следует что goto плох или хорош, из
этого следует только то, что goto -- инструмент которым
можно пользоваться неправильно. И ничего более.

Я могу выдвинуть другой критерий "читаемости" кода:
если функция не умещается в один экран -- это плохая и
негодная функция. За рядом специальных исключений, когда
можно как-то обосновать почему именно так. Где тут место
goto -- думай сам. Оно там есть.

Vitamin
29.11.2005, 13:37
Вот что нам рассказывали по поводу goto
1) безусловный переход на х86 (оттуда все пошло...) неэффективен в силу потери времени на перезагрузку конвейера. Раньше было так, как сейчас- не в курсе
2) в программах с goto труднее разбираться. вместо этого лучше табуляциями оформлять вложенности циклов и прочего. получается гораздо приятнее на вид
3) в С++ есть неявный аналог goto: throw...catch. по сути дела, то же ухо только в профиль. если редактор с подсветкой синтаксиса, то разобраться в программе гораздо проще при таком раскладе.

axor
29.11.2005, 13:39
Мужики, может подведем некую черту по теме?
Напоминаю, она звучала как "Вызов функций через RST".
Желательно "разложить" по полочкам все предложенные варианты. Ели этого никто не сделает, я попытаюсь это сделать сам, но, к сожалению, не скоро.

fk0
29.11.2005, 16:33
Предлагается патчить адрес в команде CALL.
Тогда вызов будет совершаться напрямую, а JP не нужен.


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



Также предлагается использовать не номера функций, а осмысленные символьные имена.

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

Sinus
30.11.2005, 11:02
я про спекк вообще то грю...
А вот ты возьми например Blast! - комилятор для ZX-BASIC и сделай это в нём, увидишь что будет именно так.
Я не знаю ни что такое Blast! ни что такое Tobos.
Я знаю что такое Alasm и мне этого хватает ^_~


И ещё раз напиши кроме чистого GoTo набор команд циклов - увидишь что у тебя и стек в дело пойдёт и память полетит в расход.
т.е. ты говоришь, что если Blast! не переваривает нормально цмклы, то их нигде не надо использовать?

Sinus
30.11.2005, 11:09
$ grep -r goto . | wc -l
12
Где-то за года полтора.
да. против такого аргумента не поспоришь ;)


3. А как относиться к перловым next <метка>?
так же как и к джавовским break <метка> и continue <метка>


А более взрывоопасно сделано в bash: break <число>. Теперь
попробуй правильно сосчитать скобочки...
маразм крепчал. доизбавлялись от goto ;)


Абсолютной истины нет.
воистину верная мысль!

Sinus
30.11.2005, 11:12
Мужики, может подведем некую черту по теме?
Напоминаю, она звучала как "Вызов функций через RST".
Желательно "разложить" по полочкам все предложенные варианты. Ели этого никто не сделает, я попытаюсь это сделать сам, но, к сожалению, не скоро.
По видимому придётся это делать тебе.
А я повторюсь- табличка JP в начале ПЗУ - наиболее оптимальный вариант как по размеру пямяти, так и по скорости.

axor
30.11.2005, 12:15
По видимому придётся это делать тебе.
А я повторюсь- табличка JP в начале ПЗУ - наиболее оптимальный вариант как по размеру пямяти, так и по скорости.

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

fk0
30.11.2005, 16:50
По видимому придётся это делать тебе.
А я повторюсь- табличка JP в начале ПЗУ - наиболее оптимальный вариант как по размеру пямяти, так и по скорости.

Для ПЗУ -- да, согласен. Но я считаю, смысла ориентироваться
на ПЗУ как на что-то большее, чем тест системы и начальный
загрузчик -- НЕТ. Ибо места мало и немодифицируемо. Если
нужна именно твердотельная память: загрузчик в ПЗУ, остальное
на compact flash. А тут уже другие методы могут быть. Но я
опять же считаю, CALL через массив JP xxx -- наиболее оптимально
по памяти и скорости. Прямой CALL выигрывает всего 10
тактов но неудобен жутко как для загрузки, так и для создания
загрузочного файла вообще (как в ALASM список адресов для
патча составить?) Речь про загрузку по абсолютному адресу
в т.ч. Через RST никакого смысла кроме тормозов нет вообще.
То есть смысл может быть и есть, но только там где нужен
ИМЕННО МАШИННЫЙ КОД, ни в коем случае не интерпретатор,
и нужна жуткая экономия памяти. Таких случаев, с ходу и не
назову. Там где нужно сэкономить память больше толку будет
от интерпретируемого языка (хоть бы и бейсика), от FORTH может
быть.

Vitamin
30.11.2005, 20:01
Мужики, может подведем некую черту по теме?
Напоминаю, она звучала как "Вызов функций через RST".
Желательно "разложить" по полочкам все предложенные варианты. Ели этого никто не сделает, я попытаюсь это сделать сам, но, к сожалению, не скоро.
Ну раз требують %)
В виде своих десяти копеек предлагаю прямые call на адреса функций системы, патчуемые (во словечко!) при загрузке программы.
Отличия от кернельного метода (набор jp по фиксированным адресам)
- 2 байта на адрес точки входа (которые меняются от версии к версии) против 3 байтов
- 2 байта на каждый call в настраиваемой программе. для последующей настройки. в дальнейшем эта память может использоваться под свои нужды (можно не считать)
- несколько сотен(?) байт на настройщик (проигрыш)
Выигрываем 10 тактов на выполнение каждого вызова и проигрываем несколько сотен(?) тактов на настройщик (один раз за всю работу программы)

axor
30.11.2005, 21:14
(как в ALASM список адресов для
патча составить?)

Это сделать можно, Alco где-то об этом писал и пример был. А может и не Alco это вовсе был. Тема была про релоцируемость.

jtn
30.11.2005, 22:32
В виде своих десяти копеек предлагаю прямые call на адреса функций системы, патчуемые (во словечко!) при загрузке программы.
это может делать сама прога в случае с джампами. т.е. патчить себя взяв значения оттуда.
т.е. метод со списком джампов универсальней

Vitamin
01.12.2005, 00:10
как в ALASM список адресов для
патча составить?
макросы для адресозависимых команд и все...

Vitamin
01.12.2005, 00:11
это может делать сама прога в случае с джампами. т.е. патчить себя взяв значения оттуда.
т.е. метод со списком джампов универсальней
ага. и на всякий случай еще сделать поддержку rst %)

fk0
01.12.2005, 11:45
Это сделать можно, Alco где-то об этом писал и пример был. А может и не Alco это вовсе был. Тема была про релоцируемость.

Компиляция в два адреса и сравнение. Здесь грабели подложены:



типичный код:

LD E, char
LD D, FONT / 256
...


Всё релоцируемое -- обязательно двухбайтовое. Да и вопрос в
том, как потом отличить CALL xxx на библиотеку от адресов,
сменившихся в результате сдвига начального адреса? Одни
вопросы.

Но дело ведь в том, что есть программы с абсолютным адресом
загрузки без всяких релоцируемостей. Им это всё -- лишь ненужное
усложнение. При вызове через JP xxxx никакой релоцируемости
изобретать не нужно вовсе. Просто берётся и "в лоб" ассемблируется.
После загрузки, адреса списка JP xxxx изначально были известны,
остаётся заменить номера функций на действительные адреса. Местоположение действительных адресов тоже известно, в случае
ПЗУ они (адреса) сами расположены по предопределённым заранее
абсолютным адресам. Тривиальный код получается.

fk0
01.12.2005, 11:49
макросы для адресозависимых команд и все...

Для ALASM -- да. Мне не нравится. Сильно список адресов для
патча большой получается. И потом в таком варианте не реализуется
функция-фильтр (когда в JP xxxx подсовывается её адрес вместо
действительного).

fk0
01.12.2005, 12:12
Ну раз требують %)
В виде своих десяти копеек предлагаю прямые call на адреса функций системы, патчуемые (во словечко!) при загрузке программы.
Отличия от кернельного метода (набор jp по фиксированным адресам)
- 2 байта на адрес точки входа (которые меняются от версии к версии) против 3 байтов


Для "библиотеки" -- да.



- 2 байта на каждый call в настраиваемой программе. для последующей настройки. в дальнейшем эта память может использоваться под свои нужды (можно не считать)
- несколько сотен(?) байт на настройщик (проигрыш)


Если в ROM-диск (маленький...)? И потом настраивать надо
абсолютно все программы. Через JP xxxx вызовы "библиотеки"
загруженной по абсолютному адресу (например, вызовы подпрограмм
ПЗУ) настраивать не надо.

Кроме того, ещё один ньюанс. Допустим, "библиотекой" реализуется
некий "драйвер" устройства. Например, HDD. Загрузил, пропатчил --
работай. Хорошо. А КАК БЫТЬ, КОГДА В ОДИН КОМПУТЕР НУЖНО
ЗАГРУЗИТЬ НЕСКОЛЬКО ТАКИХ "ДРАЙВЕРОВ"? Вот тут вся технология ломается. В варианте с JP xxxx на это есть два выхода:

1) для переключения между разными "библиотеками" просто
копируется массив JP xxxx целиком поверх текущего
используемого. Медленное переключение, быстрый вызов.

2) вызов по методу вызова виртуальных функций, я как-то писал.
Вызов медленный (100 тактов), переключение времени не
отнимает.



Выигрываем 10 тактов на выполнение каждого вызова и проигрываем несколько сотен(?) тактов на настройщик (один раз за всю работу программы)

В качестве компромисса я бы предложил в качестве базового
варианта всё-таки JP xxx. Экономия байта из двух на десятках
"библиотечных" функций многого не отнимет. А кому нужно очень
быстро могут поверх этого реализовывать вариант с прямым
вызовом. Наоборот тоже можно было бы, но это сложней получается
и ОЗУ больше нужно.

Vitamin
01.12.2005, 21:15
Всё релоцируемое -- обязательно двухбайтовое. Да и вопрос в
том, как потом отличить CALL xxx на библиотеку от адресов,
сменившихся в результате сдвига начального адреса? Одни
вопросы.
не факт! можно делать релоцируемые однобайтовые точки. в который раз уже даю ссылку на свою работу на эту тему. все прекрасно работает и достаточно универсально. http://zxdocs.fatal.ru/coding/module.zip


Для "библиотеки" -- да.
не только. программа от библиотеки по большому счету ничем не отличается.


Если в ROM-диск (маленький...)? И потом настраивать надо
абсолютно все программы. Через JP xxxx вызовы "библиотеки"
загруженной по абсолютному адресу (например, вызовы подпрограмм
ПЗУ) настраивать не надо.
вызовов функций пзу (в смысле кода) не так уж и много. гораздо чаще эти вызовы происходят. да и компрессию еще никто не отменял...

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

fk0
02.12.2005, 13:05
не факт! можно делать релоцируемые однобайтовые точки. в который раз уже даю ссылку на свою работу на эту тему. все прекрасно работает и достаточно универсально. http://zxdocs.fatal.ru/coding/module.zip


Опять разбираться в ассемблере... Кратко можно, на макросах?



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


Вот и я про что.




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

RST vs CALL? Смешно. Экономия байта на вызов. И потеря
сотни тактов. Равно как и CALL прямой vs через JP. Экономия
10-и тактов в подпрограмме съедающей сотни-тысячи не менее смешна. Зато геморою с компиляцией выше крыши. Не все ж в
аласме пишут. И готовые программы адаптировать невозможно практически (ну оно конечно придёт опять к варианту с JP xxxx).

Я считаю JP xxxx -- это хороший метод. Причём, обязательно,
когда оно в ОЗУ. В ПЗУ тоже, конечно, работает, но возможности
уже не те. Для библиотек загружаемых в ОЗУ массив JP xxxx всё
равно с программой таскается, так никто его не мешает и таскать
для ПЗУшного варианта. Причём полностью, все функции. Что
это даёт: настройка на ПЗУ посредством LDIR'а из ПЗУ в этот
же массив в ОЗУ. И дальше самое интересное: возможность создания функций-фильтров для библиотечных функций (трассировка
вызовов, например, и прочий хакинг).

Vitamin
02.12.2005, 13:35
Опять разбираться в ассемблере... Кратко можно, на макросах?
там на них родимых все и сделано...


Равно как и CALL прямой vs через JP. Экономия
10-и тактов в подпрограмме съедающей сотни-тысячи не менее смешна. Зато геморою с компиляцией выше крыши.
экономия 10 тактов на каждый вызов! а трата сотни-тысячи тактов один раз за запуск. вот из этих соображений и следует выбирать между скоростью и размером. про рст и речи не идет- слишком тормозно и слишком мало преимуществ.


Я считаю JP xxxx -- это хороший метод. Причём, обязательно,
когда оно в ОЗУ.
в таком случае проще вытянуть таблицу подстановочных точек как в методе пропатчения. меньше места занимать будет, а патчер и так в пзу может лежать.

GriV
02.12.2005, 19:15
Для ALASM -- да. Мне не нравится. Сильно список адресов для
патча большой получается. И потом в таком варианте не реализуется
функция-фильтр (когда в JP xxxx подсовывается её адрес вместо
действительного).

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

Кроме того, указанная таблица для преобразования вызовов функций из Call 0 в Call <ClrScr> обладает значительно большей скоростью и помехазащищённостью - потому что в ПЗУ Call <Адрес> с последующим JP <внутри_ПЗУ> всегда будет работать, а вот если вдруг версия ПЗУ не та? Вопрос отслеживания типов функций их номеров, отслеживания изменения их от версии к версии повергнет в ужаc самого автора треда и он на самом дела на медленный RST подсядет :D

А вот если будет модуль как у витамина, то программа просто напросто не запустится - ПЗУ выдаст ошибку "неверный номер функции" и например предложит прервать работу.

А теперь вот такой вопрос на засыпку - кто из писателей программ не делел такую п/п
_LDIR_ LDIR
RET

?

И после этого ктото будет говорить что это неэффективно?

Именно таких фрагментов кода, которые вообще то уже есть в ПЗУ не надо будет вставлять в свою программу - они так же будут вызываться CALL <> но для этого надо будет сделать соответствующую настройку, а она делается только один раз!!!

А сколько раз в программе такое запускается? Не один десяток, точно знаю! очистка экрана, переброс экрана, переброс других областей и т.д. и т.п. - это всё ЕСТЬ в ПЗУ, но этим мало кто пользуется потому что появляется зависимость от версии ПЗУ - в 82м ПЗУ одни точки, в 90м - другие, да мало ли программа нарвётся на какую нить версию ПЗУ кривую. Именно потому ДО СИХ ПОР версии ПЗУ почт не менялись - по крайней мере менялись такие части, которые бы минимально меняли структуру кода ПЗУ - TR-DOS как был кривой так наверное таким и останется, как был кривой БЕЙСИК 48 так таким и останется.

С системой патчей "на лету" кода всё просто - меняем систему ПЗУ - меняем в ней же точки трансляции (т.е. на что нужно подменять тот или иной Call или Jp) - и колбасим версию ПЗУ хоть как - хоть суём её часть с теневое ПЗУ хоть запаковываем его и при Reset раскаковываем в память - при "патче" программы она всё равно вызовет то что надо откуда надо а никак не приведёт к сбросу или какому нить ещё иррациональному поведению.

GriV
02.12.2005, 19:38
даже 10 прямых вызовов через Call может уже привести к выигрышу по сравнению с системой Call на Jp.

А если их много? А если их очень много?

И вообще, ещё раз - сам процесс "пропатчивания" по сравнению с временем загрузки программы (которое обязон есть) - порядка 5-10% (для средних исполнительных файлов - для игр и дем эта цифра будет значительно меньше) - т.е. вы загрузили программу, у вас ещё флопарь не перестал крутиться а программа уже настроена - остались только прямые Call и никакой таблица в памяти уже нет - чистейшая программа.

После этого ВСЕ функции выполняются быстро - потому что нет никаких дополнительных переходов и т.д.

Т.о. своя оценка (плюсом обозначаю достоинства минусом недостатки):

1. RST - слишком медленно(-) хотя может показаться удобным(+) - на каждый вызов требуется порядка 200(-) тактов (может и чуть быстрей) на определения куда прошёл вызов; может возвратиться с ошибкой(+) - если нет заданной функции. Использовался в старых программах как наследие системы прерываний от PC-шных монстров. Требуется меньше всего памяти для вызова(+) - около 3х байт, позволяет растаскивать процедуры по ПЗУ как угодно, внутренний транслятор адресов сам всё вычислит и сделает(+). Поддерживается всеми компиляторами ассемблера(+).

2. Метод установленных точек перехода - Jp (Call) на таблицу Jp внутри ПЗУ - требуется порядка 5-6 байт из программы (-), но работает очень быстро(+) по сравнению с предыдущим вариантом. Тратится условно 20/26 тактов на один вызов процедуры. Передача параметров (загрузка регистров) требует дополнительных(-) вложений (где-то до 50 тактов максимум), от компилятора требуется согласованность с версией ПЗУ (-), потому что прямой вызов/перезод на ПЗУ может привести к фатальному исходу(?-), практически невозможно контролировать "неправильные" вызовы/переходы(-). Требуется дополнительная таблица в ПЗУ(-). Поддержка компиляторами легко реализуется(+).

3. Метод прямого вызова с предварительной настройкой. Подразумевает наличие в файле таблиц для коррекции адресов вызовов(-) - что требует дополнительного места на носителе (4 байта на каждую точку). Требует предварительной настройки (-), порядка 100 тактов на одну точку вызова, однако настройка идёт только один раз(?+), больше она не выполняется. Требует 5-6 байт для своей работы(-). Скорость выполнения вызова самая высокая(+) - быстрей просто не бывает - 10/16 тактов. Загрузка регистров параметрам увеличивает(-) время вызова и память отводимую для вызова условно до 40 тактов. Имеется возможность отследить неверный вызов(+) ещё на этапе настройки вызовов - контролируются "неправильные переходы". Требуется дополнительная таблица в ПЗУ(-). Непростая реализация компиляторов - далеко не все существующие компиляторы способны генерировать соответствующий код(-).

Я для себя выбрал третий вариант, а Вы? ;)

fk0
05.12.2005, 10:30
Не такой уж и большой - она в памяти после трансляции программы по заданным адресам будет занимать 0 (ноль) байт памяти - она просто напросто не нужна после трансляции.
Та структура, которую лоббирует витамин обладет кроме того что нет проблем с вызовами и кучей других преимуществ.


У ней функциональности нет, а не проблем нет. Проблемы у неё
начинаются прямо с ассемблера. Я, например, такой код никаким
C-компилятором сгенерировать не могу, и я это знаю. Нужно будет
писать свой ассемблер для трансляции такого кода -- вот это
проблема. В ALASM можно макросами. А как быть кто пользуется
GENS? А как быть с существующим кодом? Под него писать
обёртку вокруг всё того же JP xxx.

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



Кроме того, указанная таблица для преобразования вызовов функций из Call 0 в Call <ClrScr> обладает значительно большей скоростью и


Значительно большей -- это, если грубо, примерно, 10/1000, а
то и 10/10000 -- 0.1..1% экономии по времени. И где-то
чуть по-больше по памяти, процентов 5 может быть. Несомненно,
это ЗНАЧИТЕЛЬНО большая скорость...



помехазащищённостью - потому что в ПЗУ Call <Адрес> с последующим JP <внутри_ПЗУ> всегда будет работать, а вот если вдруг версия ПЗУ не та?


Условимся, что БАЗОВЫЙ ИНТЕРФЕЙС ПЗУ вот такой-то. ОН НЕ МЕНЯЕТСЯ. Все последующие интерфейсы наследуются от базового, следовательно сохраняют функциональность предыдущих версий.
Осталость ввести функцию VERSION в базовый интерфейс.

Кому-то может нужно большего. Изобретайте COM под спектрум.



Вопрос отслеживания типов функций их номеров,


Я не знаю как можно трактовать понятие типа функции в
машинном коде. Функция это не болеe чем лишь какой-то код.
Равно как и в ассемблере.



отслеживания изменения их от версии к версии повергнет в ужаc самого автора треда и он на самом дела на медленный RST подсядет


Видно большого знатока. А ты пробовал? А я пробовал. Никаких проблем нет. Единожды пишется "заголовочный" файл:



function_name:
jp function_number
function_name:
jp function_number
function_name:
jp function_number
...


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



А вот если будет модуль как у витамина, то программа просто напросто не запустится - ПЗУ выдаст ошибку "неверный номер функции" и например предложит прервать работу.


Откуда в команде CALL xxx возьмётся неверных номер функции?
Оно может возникнуть только на этапе настройки программы на адреса библиотечных функций. Ну так это сообщение возникнет в любой системе, если такая возможность предусмотрена. Можно, например, указывать общее число функций. Можно в интерфейсе самую первую функцию выделить для реализации функций получения информации об интерфейсе и возможного переключения интерфейсов (по примеру COM). Последний вариант даже лучше.

Только, насколько я понимаю, Vitamin использует не номера, а имена функций.



А теперь вот такой вопрос на засыпку - кто из писателей программ не делел такую п/п


_LDIR_ LDIR
RET


?
И после этого ктото будет говорить что это неэффективно?


Я буду говорить. Ибо прямой LDIR в коде быстрей на 27 тактов и
не использует стек вообще.



Именно таких фрагментов кода, которые вообще то уже есть в ПЗУ не надо будет вставлять в свою программу - они так же будут вызываться CALL <> но для этого надо будет сделать соответствующую настройку, а она делается только один раз!!!


Любая настройка делается один раз. Вопрос не в том. Я утверждаю,
что "библиотечные" функции имеют, примерно, среднее время исполнения в сотни, тысячи, а то и десятки тысяч тактов. И размер
как минимум в несколько десятков байт. Экономия 10 тактов и
трёх байт в таком случае представляется бессмысленной. Что-то вроде того, что "заставь дурака богу молиться". Бессмысленно вообще измерять такты на программе 90% времени ожидающей
нажатие на кнопку или что-то в этом роде. Измерение чего-то
вроде эффективности в тактах, байтах или других величинах --
бессмысленно. Это эффективность чего? Экономии байтов? А время
потраченное на создание кода? А стоимость других затраченных
средств? А наличие механизмов отладки? (как тебе sts будет метки-то показывать в пропатченном коде?) Вопрос этот комплексный и смотреть с одного конца на него -- глупо.



А сколько раз в программе такое запускается? Не один десяток, точно знаю! очистка экрана, переброс экрана, переброс других областей и т.д. и т.п. - это всё ЕСТЬ в ПЗУ, но этим мало кто пользуется потому


Потому, что используя оптимизированную процедуру, использующую стек или LDI вместо LDIR, можно сэкономить куда больше, чем 10 тактов. На несколько порядков больше. Потому,
что процедуры в ПЗУ жутко неоптимальны. Потому, что они реализуют недостаточную или излишнюю функциональность.



что появляется зависимость от версии ПЗУ - в 82м ПЗУ одни точки, в 90м - другие,


"Точки" там примерно те же самые. В качестве эксперимента поставь
ПЗУ Basic 48 от Amstrad и убедись, что 2/3 программ перестанут работать.



или иной Call или Jp) - и колбасим версию ПЗУ хоть как - хоть суём её часть с теневое ПЗУ хоть запаковываем его и при Reset раскаковываем в память - при "патче" программы она всё равно вызовет то что надо откуда надо а никак не приведёт к сбросу или какому нить ещё иррациональному поведению.

А как программа вызовет что надо, если этого чего надо может просто не оказаться в новой версии ПЗУ?

Вопрос отнюдь не в системе вызова, а в интерфейсе. Это уже совсем другой уровень абстракции.

Vitamin
05.12.2005, 12:27
У ней функциональности нет, а не проблем нет.
есть все. и функциональность и проблемы.



Я, например, такой код никаким
C-компилятором сгенерировать не могу, и я это знаю.
хе. а то что компилятор генерит код под жестко зашитый адрес есть зер гут? или там тоже релоцируемая структура? тогда в чем проблема? это просто одна из реализаций, демонстрирующая применение идеи, формат достаточно сырой, но работающий.
если бы его не было, все бы говорили "харэ трепаться, давай код работающий!". даешь код- идет кривление лицевого интерфейса %)))



А как быть с существующим кодом? Под него писать
обёртку вокруг всё того же JP xxx.
с существующим в смысле в исходниках или в бинарниках? в первом случае проблема решается контекстным поиском и заменой по тексту или написанием спецверсии компилятора (на крайняк). а второй случай- клиника... там даже таблица jp xx не поможет...


Откуда в команде CALL xxx возьмётся неверных номер функции?
при компиляции... например юзаем старую версию системы, а программу компилируем с использованием новых заголовочных файлов. вариант с jp выдает красочный глюк на весь экран, а настройщик просто грязно матерится на несуществующие функции и нифига не запускает


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


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


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


Вопрос этот комплексный и смотреть с одного конца на него -- глупо.
Воистину!!! Знать три варианта расписали (GriV сие сделал вполне квалифицированно). Каждый может выбирать что ему выгодно. если точек входа не так много, то и таблица jp xxx сойдет, а если там развесистая клюква на 1000 с хреном вызовов, то можно и потратить сотню байт на настройщик, сэкономив килобайт на таблице

fk0
05.12.2005, 18:21
есть все. и функциональность и проблемы.


Я уже писал: невозможно динамическое создание функции-фильтра, что убивает на корню идею трассировки
вызовов. STS не показывает метки. Вообще ничего
на ходу менять нельзя. Загрузить другую библиотеку (драйвер) --
нельзя. Там если глубже копнуть, чего только не вылезет.
Да, как замена CALL-у -- оно хорошо. Но речь не про CALL, речь
про механизм вызова библиотечных функций. И о том,
какими свойствами данный механизм (не) обладает.



хе. а то что компилятор генерит код под жестко зашитый адрес есть зер гут? или там тоже релоцируемая структура?


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



тогда в чем проблема? это просто одна из реализаций, демонстрирующая применение идеи, формат достаточно сырой, но работающий.


В том, что оно теоретически может работать никто не сомневается.
Никто даже не говорит, что это плохо по каким-то причинам связанным с практической реализацей. Никто не говорит даже, что
это плохая замена для CALL -- может и хорошая. Я говорю, это
плохой интерфейс. Потому, что это всего-лишь настраиваемый
CALL. С ним неудобно работать в динамике. Его неудобно отлаживать. Его, наконец, нельзя запускать непосредственно из ПЗУ -- просто потому, что настройки потребует весь код, а не малая
интерфейсная часть, которая может быть размещена в ОЗУ, в малом его объёме (речь пока не идёт про практическое использование тех или иных решений).



если бы его не было, все бы говорили "харэ трепаться, давай код работающий!". даешь код- идет кривление лицевого интерфейса %)))


Увы, это так. Тут нужна большая и серьёзная "теоретическая" работа. Код писать дело нехитрое, обезьянья работа в сущности.
Других дел в жизни хватает. И писать код в мусорную корзину
тем более мало кому захочется.



с существующим в смысле в исходниках или в бинарниках? в первом случае проблема решается контекстным поиском и заменой по тексту или написанием спецверсии компилятора (на крайняк). а второй случай- клиника... там даже таблица jp xx не поможет...


(речь про интеграцию существующего кода в систему)

На счёт поиска и замены я не уверен. Я даже считаю, невозможно абсолютно любой код под твои макросы адаптировать. "Релокатор"
наверняка не все выражения ассемблера поддерживает. В противном
случае мы придём к компиляции перед выполнением.

Я конечно же имел ввиду двоичный код. JP xxxx только и остаётся,
если обернуть его в этот интерфейс.



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


А мы возьмём и на шнуре от модема повесимся. И от этого умрём. Поэтому модемы -- абсолютное зло. Аргументация того же уровня.

Для невнимательных: ИНТЕРФЕЙС НЕ МЕНЯЕТСЯ, ТОЛЬКО НАСЛЕДУЕТСЯ. Или, как вариант, имеется обязательный базовый
интерфейс позволяющий на ходу получать информацию об используемом интерфейсе и, как вариант, переключать разные
версии интерфейсов. В целом верно одно: всегда доступен
базовый интерфейс. Через который можно выявить используемый
интерфейс и возможную несовместимость. Это возможно на этапе
"настройки" программы.



настройщик просто грязно матерится на несуществующие функции и нифига не запускает


Вариант с JP xxxx тоже, как ни странно, требует настройки.
Ещё раз: массив JP xxxx таскается по раздельности В КАЖДОЙ
ЗАГРУЖАЕМОЙ ПРОГРАММЕ. При загрузке обновляется из массива
JP xxxx доступного в библиотеке. Почему так: потому, что адреса
в конкретных командах CALL и JP адресуют именно свой "локальный"
массив, потому что именно его адрес известн в момент компиляции,
а адрес загрузки библиотеки -- неизвестен. В качестве оптимизации,
при размещении библиотеки по фиксированному адресу в ПЗУ предлагается прямая адресация массива JP xxxx размещённого по
известному адресу в ПЗУ. Но такой способ адресации является
НЕЖЕЛАТЕЛЬНЫМ, потому, что ограничивает возможности использования интерфейса в части фильтрации, трассировки и т.п.
И в таком случае, поскольку настройка становится "ненужной" дейстивтельно есть возможность напороться на проблему. Но против этого, как и при действительной настройке, достаточно лишь вызвав соответствующую функцию БАЗОВОГО ИНТЕРФЕЙСА, ГАРАНТИРОВАННО ИМЕЮЩУЮСЯ В ИНТЕРФЕЙСЕ, можно выяснить насколько имеющаяся библиотека совместима с требуемой.



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


Мне тоже было бы жалко. Потому как практического смысла не
имеет. Лучше решить проблему идентификации интерфейсов. И кроме того, символьные имена уровня ассемблера -- как их сделать доступными на уровне кода? Опять же серьёзные ограничения технического плана. ALASM может и умеет, а GENS опять нет. Или ZASM скажем -- не умеет.



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


Речь про библиотеку функций с ЧЁТКО ОПРЕДЕЛЁННЫМ ИНТЕРФЕЙСОМ, или про несвязный набор фрагментов кода?



имеются в виду накладные расходы на вызов процедуры. умноженные на количество фактических вызовов...


Вот имеется функция ожидания нажатия на кнопку. Хрен ли толку, если ты на ней 10 тактов сэкономишь? Если для тебя действительно
критична скорость: импортируй тескст (ассемблерный) функций и
откажись от библитечных. Ибо 27 тактов на CALL и RET сверх того --
тоже не мало. Экономить 10 тактов, чтоб тут же отдать 27 (на итерацию) -- вот этого я не понимаю. И не хочу понимать. Это бред.
Кроме того, итеративные функции стоило бы переписать, чтоб цикл
перенести в библиотеку. Условие окончания цикла можно определить функцией, указатель на которую передаётся аргументом при вызове библиотечной функции -- вот где экономия.



Воистину!!! Знать три варианта расписали (GriV сие сделал вполне квалифицированно). Каждый может выбирать что ему выгодно. если точек входа не так много, то и таблица jp xxx сойдет, а если там развесистая клюква на 1000 с хреном вызовов, то можно и потратить сотню байт на настройщик, сэкономив килобайт на таблице

Если там клюква на 1000 вызовов, по 50 байт на вызов -- займёт
всю память и ничего не останется... Для клюквы нужны свои, специфические решения.

Речь про что-то более общее. Где вызов занимает сотни-тысячи, реже десятки тысяч тактов и десятки, реже сотни байт. Где общее число вызовов на модуль -- порядка единиц-десятков. Пример?
"Драйвер" модема, CMOS, HDD, CDROM... Модуль арифметики с
плавающей запятой (откуда там сотни вызовов? Столько функций
не наберётся). Библиотека строковых функций. Библиотека
поточного (побайтового) ввода-вывода для TR-DOS. Упаковщих
HRUST. Текстовая консоль, драйвер "мыши" со стрелочкой, примитивный (максимум уровня ZXASM) "GUI" интерфейс. Может быть в последнем случае можно превысить предел в 100 вызовов. В таком случае, интерфейс можно разбить на 2-3 части (предел -- 85 виртуальных функций на объект).

GriV
05.12.2005, 18:40
2fk0> я со многими твоими доводами согласен, а ты же с моими (и витаминовскими) доводами принципиально соглашаться не хочешь, даже не хочешь рассматривать варианты вызовов другого типа (?). Я ж написал уже в сравнительной таблице какие достоинства и недостатки того или иного метода - так что каждый в праве выбирать что он хочет. Я СЧИТАЮ (т.е. это IMHO) что все другие методы имеют право на жизнь, но именно постольку поскольку должна быть альтернатива - я же их предпочитаю не использовать (свой выбор я уже описал), а потому считаю что почти раскрыл тему треда (упустив может быть какой нибудь совсем раритетный способ вызова). А посему просто не буду продолжать творить флейм ;))))) :v2_wink:

P.S. Оффтоп на 100%, но по жизни очень правильный совет. Наш препод по философии всегда нас учил поступать следующим образом: "Прежде чем критиковать точку зрения прочувствуй на своей шкуре все её достоинства и недостатки, ты не любишь так делать потому твоё мировоззрение очень ограниченно." В этом смысле я разделил твою (fk0) точку зрения, я САМ пробовал писать таким образом и выстрадал все плюсы и минусы системы, потому считаю что имею достаточно объективную точку зрения.

captain cobalt
05.12.2005, 19:50
Дополнительные идеи:

1. Добавить в модуль директивы выравнивания, чтобы он загружался например по адресу кратному 256.

2. Пропатчивать не только непосредственными значениями, но и значениями, преобразованными по формуле. Формулы записывать в объектный файл модуля в обратной польской записи.

GriV
05.12.2005, 20:29
Дополнительные идеи:

1. Добавить в модуль директивы выравнивания, чтобы он загружался например по адресу кратному 256.

2. Пропатчивать не только непосредственными значениями, но и значениями, преобразованными по формуле. Формулы записывать в объектный файл модуля в обратной польской записи.

Лучше это в отдельный тред отправить.

Vitamin
05.12.2005, 21:00
2fk0. не буду тянуть баян с цитатами-ответами, влом. GriV можно сказать выразил витавшие в моем котелки мысли.

ишшо одна мысля возникла. значица стоит у нас такая глобальная цель- сляпать модульную структуру, каждый модуль внутри имеет таблицу, которая заменяет/пришпиливается к основной таблице точек входа.
вопрос нумбер адын: модуль релоцируемый? если нет, то идея не стоит ломаного яйца или выеденного гроша (кому как нравится %)) если релоцируемый, то см. выше.
вопрос нумбер два: а в какоме именно место будем пришпиливать/заменять таблицу виртуальных функций данного конкретного модуля?
может я чтото недопонял, но...




Дополнительные идеи:

1. Добавить в модуль директивы выравнивания, чтобы он загружался например по адресу кратному 256.

2. Пропатчивать не только непосредственными значениями, но и значениями, преобразованными по формуле. Формулы записывать в объектный файл модуля в обратной польской записи.
угу. п1. имхо можно решить, пожертвовав несколькими битами в конфигурационном байте (абсолютно без проблем). насчет п2. идея стоящая, но дело имхо немного упирается в сложность создания польской записи. если не ошибаюсь, в модулях для is-dos такое практиковалось. мож просветит кто, насколько успешно?

GriV
05.12.2005, 21:22
Народ! оффтоп пошёл!

fk0
06.12.2005, 13:49
2fk0> я со многими твоими доводами согласен, а ты же с моими (и витаминовскими) доводами принципиально соглашаться не хочешь, даже не хочешь рассматривать варианты вызовов другого типа (?). Я ж написал уже в сравнительной таблице какие достоинства и недостатки того или иного метода - так что каждый в праве выбирать что он хочет. Я СЧИТАЮ (т.е. это IMHO) что все другие методы имеют право на жизнь, но именно постольку поскольку должна быть альтернатива - я же их предпочитаю не


Проблема в том, что ты эту альтернативу выбираешь за других. В частности, за меня.

Я указал, где считаю место альтернативным методам -- что-то
очень специфическое по памяти (RST) или по-скорости (прямой CALL). Ибо и там и там имеет место экстремальная экономия
какого-либо одного ресурса в ущерб всем остальным. Я не считаю
такое решение приемлимым в общем случае. Вот и всё что я хочу
сказать. Я не отвергаю такие решения для тех самых экстремальных
случаев, я даже говорю, они могут существовать параллельно в рамках одного интерфейса, представимого тремя ортогональными
понятиями: собственно интерфейс, метод доступа к функциям интерфейса, и соглашения о совместом использовании аппаратных
ресурсов, передаче аргументов и т.п. Это просто разные вещи.
Один и тот же интерфейс может иметь совершенно разные свойства
по двум другим направлениям и это будет тот же самый интерфейс
в плане функциональности, но другой интерфейс в том смысле, что
может быть недоступен в какой-то программной среде. Впрочем
об этом нужно писать отдельно, для чего лучше вынести в отдельную тему: http://zx.pk.ru/showthread.php?t=1950



таким образом и выстрадал все плюсы и минусы системы, потому считаю что имею достаточно объективную точку зрения.

Объективной точки зрения не существует.

captain cobalt
08.12.2005, 01:43
Таблица JP работает неплохо, если речь идёт о прошивке ПЗУ.

Теперь предположим, что мы хотим использовать таблицы JP и для вновь разрабатываемых модулей, предназначенных для работы в ОЗУ. Тогда каждому модулю понадобится таблица JP на его процедуры. Мы не можем каждому модулю для его таблицы JP назначить адрес, который не будет менятся всю оставшуюся жизнь. Значит таблицы JP должны быть перемещаемыми. А если они должны быть перемещаемыми, то потребуется пропатчивать все CALL-ы в эти таблицы. А если понадобится пропатчивать CALL-ы, то зачем нужны эти таблицы? ;)

fk0
08.12.2005, 11:06
Таблица JP работает неплохо, если речь идёт о прошивке ПЗУ.


Это всё ровно наоборот. В ПЗУ как раз плохо по ряду других
причин...



Теперь предположим, что мы хотим использовать таблицы JP и для вновь разрабатываемых модулей, предназначенных для работы в ОЗУ. Тогда каждому модулю понадобится таблица JP на его процедуры. Мы не можем каждому модулю для его таблицы JP назначить адрес, который не будет менятся всю оставшуюся жизнь. Значит таблицы JP должны быть перемещаемыми. А если они должны быть перемещаемыми, то потребуется пропатчивать все CALL-ы в эти таблицы. А если понадобится пропатчивать CALL-ы, то зачем нужны эти таблицы? ;)

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

captain cobalt
08.12.2005, 16:27
Вот именно. Адрес таблицы должен быть фиксированным.

Это не особо мешает, если таблица одна единственная.
Но что если таблиц две или больше?
Кто выбирает адреса для таблиц?
Что если независимые разработчики выберут пересекающиеся адреса?
Распределять адреса централизованно? Но это получится make world. ;)

На оффтопиках действительно применяется этот способ. Таблица вместе с кодом лепится в одно целое. Но там команды CALL относительные, поэтому всегда показывают в таблицу независимо от того по какому адресу загрузили модуль. Но на Z80 нет относительных CALL, только короткие JR.

fk0
08.12.2005, 17:20
Вот именно. Адрес таблицы должен быть фиксированным.


"Относительно меня или вас?" (Ц)

Он прекрасно фиксированный относительно каждой программы
с которой КОПИЯ данной таблицы скомпонована. Понимаешь,
КОПИЯ. Много их. Таблиц этих. Каждой программе -- своя. И ещё
одна на библиотеку, СВОЯ КОПИЯ. Поэтому её адрес известен,
как известен адрес любой процедуры в своей программе.



Это не особо мешает, если таблица одна единственная.
Но что если таблиц две или больше?
Кто выбирает адреса для таблиц?


Ассемблер. При компиляции.



Что если независимые разработчики выберут пересекающиеся адреса?


Тогда две программы не загрузить одновременно. Или две
библиотеки. Потому пишите релоцируемый код. И таблица
сама релоцируемой получится. КОПИЯ таблицы то-есть,
локальная. А потом, когда всё будет загружено, информация
из ГЛАВНОЙ ТАБЛИЦЫ, которая связана с программой функции
которой вызываются через эту таблицы, должна быть скорпирована
во ВТОРИЧНЫЕ ТАБЛИЦЫ, которые используются сторонними
программами для вызова функций из библиотеки или программы
с которой связана ГЛАВНАЯ ТАБЛИЦА. Что тут непонятного?



На оффтопиках действительно применяется этот способ. Таблица вместе с кодом лепится в одно целое. Но там команды CALL относительные, поэтому всегда показывают в таблицу независимо от того по какому адресу загрузили модуль. Но на Z80 нет относительных CALL, только короткие JR.

Я только что описал, как работает заменитель "относительных CALL". Именно это он и позволяет. Вызов по адресу известному
относительно собственного программного модуля.

captain cobalt
08.12.2005, 17:53
Тогда две программы не загрузить одновременно. Или две библиотеки. Это полный отстой и mustdie.

Только по одной этой причине такой способ - "ф топку".

А ещё при перекомпиляции библиотеки понадобиться перекомпилировать все зависимые программы.
make world
Тьфу.

Лишь ПЗУ это не касается. ПЗУ всегда по одному адресу. И таблицу ему можно сделать по фиксированному адресу.

Потому пишите релоцируемый код. И таблица сама релоцируемой получится. Ну конечно.
Каждый раз, прежде чем делать CALL нужно вычислять адрес, по которому делать этот CALL.
Каковы накладные расходы на время выполнения этих вычислений и на память для хранения их кода?
Не превышают ли они расходов на единовременное пропатчивание?

fk0
08.12.2005, 18:06
Это полный отстой и mustdie.
Только по одной этой причине такой способ - "ф топку".
А ещё при перекомпиляции библиотеки понадобиться перекомпилировать все зависимые программы.
make world
Тьфу.


Ты это, текст не по-диагонали читай, да? Я же ясно сказал --
таблиц несколько и адрес у каждой свой, произвольный.
Он настраивается перед пуском.



Лишь ПЗУ это не касается. ПЗУ всегда по одному адресу. И таблицу ему можно сделать по фиксированному адресу.


Можно. Вопрос какую: собственную таблицу ПЗУшных программ -- да. Таблицу, используемыю загружаемыми программами -- тоже можно, но тогда возникает описываемая тобой проблема, именно поэтому так делать и нежелательно, и можно не делать, а пользоваться своей таблицей размещённой в ОЗУ. Которая по сути
дела является полной копией ПЗУшной с той лишь разницей,
что её адрес (той которая в ОЗУ) -- известен. НО ОН НЕ ФИКСИРОВАННЫЙ. Верней, не обязательно фиксированный,
если программа релоцируемая. Что тут непонятного?



Ну конечно.
Каждый раз, прежде чем делать CALL нужно вычислять адрес, по которому делать этот CALL.


Чушь. Для программ с абсолютным адресом загрузки просто
LDIR делается или иным способом копируется таблица из ПЗУ.
Адрес которой (ПЗУшной) тоже может быть не фиксированный.
Для релоцируемых программ, при их настройке на адрес запуска,
адрес собственной таблицы настраивается АВТОМАГИЧЕСКИ!
А адреса в этой таблице опять же копируются из ПЗУ.



Каковы накладные расходы на время выполнения этих вычислений и на память для хранения их кода?


Времени -- 10 тактов. Памяти 3*N, где Ni -- число "внешних" по отношению к загруженнной программе функций.



Не превышают ли они расходов на единовременное пропатчивание?

За бесконечный период времени -- превышают (oo*10 == oo).

captain cobalt
08.12.2005, 19:24
Я же ясно сказал -- таблиц несколько и адрес у каждой свой, произвольный.
Он настраивается перед пуском. Ну хорошо.
Действительно, динамический компоновщик может это делать.

НЕ ФИКСИРОВАННЫЙ. Верней, не обязательно фиксированный, если программа релоцируемая. Что тут непонятного? Понятие "программа релоцируемая" подразумевает пропатчивание адресов в CALL на локальную перемещаемую таблицу?

Времени -- 10 тактов. Памяти 3*N А во время загрузки кроме LDIR?

fk0
09.12.2005, 11:23
Ну хорошо.
Понятие "программа релоцируемая" подразумевает пропатчивание адресов в CALL на локальную перемещаемую таблицу?


Да. И не только их. А любых ссылок к локальным меткам по
абсолютному адресу (в машинном коде). Потому и говорю --
получается автомагически, в том смысле что выделять CALL
функций конкретной библиотеки от CALL на любой другой
локальный адрес специальным образом не надо. Достаточно
сравнить две версии программы откомпилированные по
разным адресам.



А во время загрузки кроме LDIR?

Если в локальных таблицах ссылки на все функции -- достаточно
и LDIR. Если только ссылки на используемые -- ну тут несколько
по-сложней будет, но смысл такой-же, просто копироваться будут
конкретные кусочки таблицы.

captain cobalt
09.12.2005, 12:45
Теперь замечаем что:

1. Релоцируемость - это пропатчивание CALL относительно базы своего модуля
2. Динамическая компоновка - это пропатчивание CALL относительно базы другого модуля

Таким образом, если есть релоцируемость, то тем самым есть почти всё необходимое для нормальной динамической компоновки.
Не так ли?

fk0
09.12.2005, 15:22
Теперь замечаем что:

1. Релоцируемость - это пропатчивание CALL относительно базы своего модуля


Не только CALL, а абсолютны всех адресов.



2. Динамическая компоновка - это пропатчивание CALL относительно базы другого модуля


Любых адресов относящихся к импортируемому модулю.



Таким образом, если есть релоцируемость, то тем самым есть почти всё необходимое для нормальной динамической компоновки.
Не так ли?

Не так. Ибо [база своего модуля] != [база чужого модуля].
И чтобы знать чего патчить нужно к каждому адресу приделать
ярлык с указанием модуля к которому он относится. В настояшее
время ни один ассемблер такого не поддерживает.

captain cobalt
09.12.2005, 18:29
И чтобы знать чего патчить нужно к каждому адресу приделать ярлык с указанием модуля к которому он относится. Осталось лишь преодолеть это затруднение для полного счастья.

В настояшее время ни один ассемблер такого не поддерживает. Это действительно серьёзная проблема.
Решение только для одного ассемблера понравится далеко не всем.
Вероятно, проблему следует решать разработкой ассемблера, который сможет заменить все другие ассемблеры.

fk0
12.12.2005, 11:09
Осталось лишь преодолеть это затруднение для полного счастья.


Вот как его преодолеть, например, для ZXASM 3.00 ?



Решение только для одного ассемблера понравится далеко не всем.

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

Это сильно сказано. "РЕШЕНИЕ ТОЛЬКО ДЛЯ ОДНОГО АССЕМБЛЕРА..." -- следовательно разработка ОДНОГО АССЕМБЛЕРА
ничего не даст, это очевидно.

Я смотрю с другой стороны. За базовый метод можно взять вариант с JP xxx. Ибо из этой структуры легко извлекается информация
потребная для функционирования любого другого метода. Кому надо позарез как прямой вызов -- пусть как хочеть патчит свои вызовы любым методом, а адреса из таблички JP xxx может извлекать сразу.
Кому нужно именно RST #10 -- аналогично. Пиши свой код, адреса
бери из той же таблички. Патчить все программы по методу прямого
вызова -- невозможно. Использовать абсолютно везде RST -- тоже
невозможно. По чисто-программным ограничениям.

GriV
19.01.2006, 15:49
2fk0> на самом деле тут есть несколько подводных камней.

Я согласен, любое ускорение (в том числе и использование текущих средств разработки как ускорение процесса) несёт в себе отдачу по другим ресурсам - своего рода "плата" за скорость.
А теперь конкретней:

1. Внешняя память - её есть практически (!) сколько угодно
Пример: гибкие диски, жёсткий диск, компакт диск
1.а. Использование в методе модульных структур система использует эту память
1.б. Использование кернального метода не подразумевает перенос своего рода "нагрузки" на эту часть компьютера

2. Внутренняя память
Пример: ОЗУ
Спецификой является невозможность прямой адресации произвольной точки, только через страничный механизм доступа. Размер страницы - 16кб.
2.а. Модульная структура - программа занимает ровно столько сколько есть, и на строчкой больше
2.б. Каждый вызов в ОЗУ дополняется строчкой перехода по длинному адресу.

3. Релоцируемость
Понимается перенос программы уже скомпилированной в любой произвольный адрес
Пример: внутри программу переход во внутреннюю часть:
Сall Internal_label1
...
Internal_label1 ld a,1
...
Модификация метки внутри программы
ld a,5
ld (mem_label1+1)
...
mem_label ld h,0
...
3.а. Модульная система - как угодно и куда угодно - это её главный бонус
3.б. Кернальная система - не понял я как релокации настраиваются (если вообще есть такая возможность), точнее говоря я понял что это делается непросто

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


А теперь самое главное (своего рода закусь):

Те, кто писал ОСи под спекк сталкивались с проблемой нехватки ОЗУ - программа, написанная и откомпилированная не хотела занимать меньше 1ой страницы - т.е. сколько есть страница памяти - столько максимально (ну или почти столько) можно было загрузить приложений.
Это связано с тем, что трудно предсказать где должна закончиться одна программа (её код + служебные данные) и соответственно оттуда же начаться другая. Потому как правило обходились компиляцией под адрес #C000.
Т.е. если есть уже загруженная программа с адреса #c000 и длиной скажем #1AF0 то нужно чтобы следующая загружаемая п/а имела адрес компиляции #DAF0 - и никак не меньше, хотя больше адрес можно. А если в следующий раз программа будет иметь тот же стартовый адрес а длину уже #2AF0 - как быть?
В этом и кроется одна из причин возникновения т.н. динамической компиляции - неизвестно заранее куда должна быть загружена программа (базовый адрес).
Касательно систем записи программ.
Модульная система - теперь программ можно загрузить именно столько, сколько есть памяти, привязываясь к страничному принципу лишь ЧАСТИЧНО. Я думаю из указанного выше примера будет ясно почему.
Кернальный принцип - ровно там где были там и остались - т.е. на каждый процесс будет уходить по 1 странице памяти.

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

Сама ОСь может быть даже потимизирована (читай - обрезана) под 48 к машины и худо бедно на ней приложения в модульной структуре но будут запускаться. Касательно кернальной системы - больше 1го приложения загрузить не сможем (точней загрузим но не запустим, нет релокации).




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

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

GriV
19.01.2006, 16:01
2fk0> вообще говоря этот способ вызова п/п из других п/п сильно пересекается с другими элементами - ну я думаю понятно с какими конкретно - и является своего рода оптимумом для ОС на ZX.

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