программного префикса
После того, как найден адрес блока памяти, необходимо опреде-
лить общее количество памяти, необходимое для сохранения. Разли-
чия между программами .COM и программами .EXE здесь становятся
уже более заметными. Для программы типа .EXE размер должен быть
определен путем вычитания адреса начала сегмента PSP из адреса
сегмента фиктивного сегмента, расположенного в конце программы,
как показано в листинге 2-12. Почему используются адреса сегмен-
тов? Функция 4Ah ожидает размер в параграфах, а адрес сегмента
обычно является адресом параграфа.
Листинг 2-12. Функция 4Ah - "Модифицировать блок распределенной
памяти" - изменение размера программы типа .EXE
-----------------------------------------------------------------
resize PROC NEAR
mov ax,es ; получение адреса PSP
- 2-41 -
mov bx,SEG end_addr ; получение адреса сл. сегмента
sub bx,ax ; разность - размер программы
mov ah,04Ah ; модификация распредел-й памяти
int 21h ; вызов MS-DOS
jnc short resize_ok ; нет переноса => хорошо
mov ax,04C00h ; перенос => сбой--авар. заверш.
int 21h
resize_ok:
ret
resize ENDP
;
; Оставшаяся часть программного кода в этой программе END_ADDR
; является последним элементом перед предложением END. Это
; сделано для того, чтобы предложение END_ADDR связалось бы как
; последний сегмент, если используется более одного исходного
; файла.
;
end_addr SEGMENT
end_addr ENDS
END
-----------------------------------------------------------------
Для программы типа .COM требуется меньше хлопот. В отличие от
программы типа .EXE, которая имеет определенный размер, установ-
ленный компоновщиком LINK, программы типа .COM могут изменять
свой размер. Размещение стека в программе типа .COM, которое ус-
танавливается MS-DOS, может изменяться от конца сегмента (FFFEh)
на 256 байтов больше программы (минимальный размер, требуемый
MSDOS для стека). Пользователь может выбирать первое, принимая,
что MS-DOS обеспечила и изменила размер стека (установленный раз-
мер 64 кбайт (1000h) параграфов) или все, что осталось; или вто-
рое, пересылая стек и изменяя размер, основываясь на этом. Второй
выбор освобождает больше памяти и, таким образом, является более
предпочтительным и рекомендуемым фирмами "Майкрософт" и "ИБМ".
Листинг 2-13 содержит пример программы .COM, который устанавлива-
ет свой собственный стек и изменяет размер своего начального бло-
ка распределения на более умеренный размер.
Листинг 2-13. Функция 4Ah - "Модифицировать блок распределенной
памяти" - изменение размера программы типа .COM
-----------------------------------------------------------------
code_seg SEGMENT
ASSUME cs:code_seg
ORG 0000h
seg_org EQU $
ORG 0100h
main PROC FAR
start:
mov sp,offset stack
call resize
;
; здесь может выполняться оставшаяся часть программы
;
main ENDP
resize PROC NEAR
mov bx,(offset last_byte - seg_org + 15) shr 4
- 2-42 -
mov ah,04Ah ; модификация распр-й памяти
int 21h ; вызов MS-DOS
jnc short resize_ok ; нет переноса => хорошо
mov ax,04C00h ; перенос => сбой--ав.заверш.
int 21h
resize_ok:
ret
resize ENDP
db 32 DUP ('stack ')
stack:
last_byte EQU $
code_seg ENDS
END start
-----------------------------------------------------------------
Интересной частью этой программы является способ определения
размера результирующей программы. Для преобразования количества
байтов программы в количество параграфов по-существу посредством
деления на 16, используется оператор SHR макроассемблера MASM.
Что не является таким очевидным так это то, почему seg_org вычи-
тается из смещения last_byte. Оператор SHR не выполняется, когда
применяется для смещения, и он вырабатывает сообщение об ошибке
"Constant was expected" (ожидалась константа). Однако, разность
между двумя смещениями считается постоянной, делая выражение при-
емлемым для макроассемблера MASM. Заметим, что seg_org должна
иметь нулевое смещение так, чтобы размер был относительно начала
сегмента. Если бы использовалась метка start, то были бы потеряны
последние 100 (шестнадцатиричное значение) байт программы. (Заме-
тим, что last_byte: для вычислений работает также хорошо, как и
last_byte equ $).
Кроме того, для успешного использования освобождения памяти
прием вычитания двух смещений (либо метки, либо количества) для
получения константы, может быть успешно использован для всех ти-
пов операций, где размер требуется в выражениях, использующих
константы. Рассмотрение этого применения для задач выравнивания
буферы данных на границу параграфа будет приведено в главе 6.
Распределение памяти из языков высокого уровня
Большинство языков высокого уровня обрабатывает проблемы,
связанные с распределением или изменением размера блоков памяти.
Пользователю не нужно добавлять программы изменения размера блока
начального распределения в процессе использования функций управ-
ления памятью языка высокого уровня. Например, в библиотеке языка
программирования Си функции malloc и calloc работают независимо
от начального распределения памяти.
Защита данных и управление областью действия данных
Технические приемы, используемые при повторно входимом коди-
ровании, подвели нас к другому аспекту модульного программирова-
ния: защите данных в программе от случайного изменения. Разру-
шение ценных данных наиболее часто происходит тогда, когда
одна часть программы ошибочно изменяет данные, относящиеся к дру-
- 2-43 -
гой части программы. Путем следования некоторым основным правилам
возможность такого события может быть значительно уменьшена. Са-
мое главное правило состоит в том, что модульность данных прог-
раммы также хороша как и модульность программного кода, управляю-
щая рядом данных, к которым подпрограмма может иметь доступ. Это
понятие очень часто называется "областью действия данных". Обра-
тимся к тому, что мы уже изучили, и посмотрим как это можно при-
менить для наших новых проблем.
Локальная память в сравнении с глобальной памятью
Память человека в любое конкретное время может иметь дело
только с ограниченным количеством понятий. Для программистов это
подразумевает то, что с ростом количества манипулируемых и запо-
минаемых элементов растет и количество сделанных ошибок. При ис-
пользовании локальной памяти для подпрограмм программист уменьша-
ет количество элементов данных, которое должно быть запомнено.
Чем иметь дело с областями данных, содержащими сотни переменных,
программист может теперь иметь дело с областями данных, содержа-
щими небольшое количество данных. Может существовать небольшое
количество "узких" областей данных, каждая из которых может быть
проверена программой, использующей ее, потому что каждая область
безопасна только тогда, когда есть уверенность, что другие прог-
раммы не связаны с ней. Любой из представленных способов для пов-
торно-входимых программ служит для распределения временной локаль-
ной памяти данных.
Глобальные области данных, также известные как общие области,
могут быть разбиты на модули. В этом случае вместо одной монолит-
ной области данных создается некоторое количество узких областей
данных. После этого подпрограммы будут иметь дело только с такими
участками глобальных данных, которые требуются для обработки. Это
накладывает на часть программистов требование быть внимательными
и осторожными с директивами ASSUME в содержимом регистров сегмен-
та, но такая явная обработка общих данных к тому же делается чи-
ще, чем доступ и затем изменение критических данных. Например,
общая область данных, содержащая строки текста и символьные конс-
танты, не нуждается в части подпрограмм вычисления чисел, также
как таблицы значений синуса и косинуса не нужны подпрограмме вво-
да информации с терминала.
В стек должно передаваться столько параметров, сколько необ-
ходимо для уменьшения количества внутренних обращений к данным.
Всякий раз, когда несколько программ должны иметь доступ к облас-
тям данных с целью передачи параметров, вероятность ошибки увели-
чивается.
Общие данные обычно должны быть определены с помощью директи-
вы DEFINE DATA (определить данные) так, чтобы содержимое области
оставалось без изменения и не было субъектом случайного удаления
при ошибочном освобождении программой с помощью функции "Освобо-
дить распределенную память".
Использование регистров сегмента
Регистры сегмента позволяют программисту ограничивать диапа-
зоны ссылок на данные. Путем изменения базы сегмента, содержащего
данные, архитектура машины автоматически вынуждает программу к
"окну" доступа в 64 Кбайт. Если более восприимчивые данные поме-
щаются в нижние области памяти, то после того, как регистр сег-
- 2-44 -
мента изменится для указания на высший адресуемый блок памяти,
данные в нижней области памяти будут полностью защищены от несанк-
ционированного доступа.
Управление размером доступных данных
Программист в дальнейшем может управлять размером окна данных
путем установки bounds-checking (проверка границ) в массиве, к
которому осуществляется доступ. Одна из наиболее типичных ошибок
в данных происходит тогда, когда доступ к массиву выполняется че-
рез его границы. Все, что происходит на границе массива, теряет-
ся. Проверка границы массива может быть выполнена с помощью прос-
того макроса, как показано в листинге 2-14. Для программистов,
работающих с процессором 80x86, для выполнения этой проверки пре-
дусмотрена инструкция BOUND (граница). Для обеспечения совмести-