14.5. Процедура перемещения данных Move

Процедура Move в некотором роде — уникальная. Она практически не имеет аналогов в стандартах языков высокого уровня. По своей сути она скорее является командой машинного уровня. Процедура содержит три параметра:

Move( VAR Source, Dest; NBytes : Word )

и служит для перемещения в ОЗУ блоков данных размером NBytes. Начало блока задается переменной Source любого типа (первый байт блока соответствует первому байту значения Source). Переменная Dest, точнее задаваемый ею первый байт, указывает на место в

- 302 -

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

Очень эффективно работает процедура Move в различных трюках программистов, таких как копирование области видеопамяти (см. пример к процедуре Keep модуля DOS). Но не менее эффективно ее можно использовать и в более традиционных задачах. Например, копирование массива B в массив A:

А := В;

может быть выполнено процедурой

Move( В, A, SizeOf( В ) );

т.е. блок байтов — значений массива B соответствующего размера — будет скопирован в область памяти, занимаемую массивом A. В этом случае применение Move не дает особого эффекта. Но если, например, нам надо скопировать одну структуру в другую, причем сами структуры — разнотипные, то Move позволяет проделать это максимально просто и с наибольшим быстродействием (рис. 14.5). В этом случае не надо уже беспокоиться об индексах, а надо только указать размер копируемого блока.

| VAR R : RECORD { запись R : } X, Y :Integer; { поля-числа } Arr : Array [ 20..120 ] of Real; { поле-массив } S :String { поле-строка } END; A : Array [ 1..500 J of Real; { массив А } i : Word; BEGIN {Традиционное решение: for i:=20 to 120 do A[ i-19 ] := R.Arr[ i ]; } { Оптимальное решение : } Move( R.Arr, A, SizeOf( R.Arr ) ); END.

Рис. 14.5

Еще одна область приложения, где Move эффективнее прочих способов — это перемещение значений (сдвиг) внутри одного массива. Пусть, к примеру, объявлен массив

VAR

A : Array[ 1..10000 ] of Real;

- 303 -

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

for i:=1 to 1000 do A[ i+100] := A[ i ];

который лишь испортит данные массива A. Это произойдет из-за перекрытия исходного и конечного отрезков значений (рис. 14.6).

Рис. 14.6

Здесь элементы, начиная со 101-го, будут замещены раньше, чем скопированы! Корректный цикл FOR должен быть убывающим:

for i:=1000 downto 1 do A[ i+100 ] := А[ i ];

Чтобы избежать всех этих сложностей, рекомендуем пользоваться процедурой Move. Рассматриваемая задача решается так:

Move( А[ 1 ], А[ 101 ], 1000 * SizeOf( Real ) );

При этом проблема перекрытия снимается автоматически самой процедурой Move (она выбирает направление копирования исходя из заданных параметров). Параметры A[1] и A[101] в вызове имеют смысл не значений 1-го и 101-го элементов массива A, а адресов этих элементов в памяти.

Если же понадобится вернуть 1000 элементов «назад» в первую позицию массива, вызов будет таким:

Move( А[ 101 ], А[ 1 ], 1000 * SizeOf( Real ) );

и проблема перекрытия вновь будет решена автоматически.

Помните, что никаких проверок на корректность значений параметров в Move не происходит, и если значение длины блока больше, чем может вместить переменная — адрес назначения (второй параметр Move), то будет заполнена часть памяти, отведенная под другие

- 304 -

данные, что весьма неприятно. Советуем использовать функцию SizeOf для вычисления длин блока. Выражения типа 1000*SizeOf (Real) состоят из констант и могут быть вычислены еще во время компиляции программы, так что длинные арифметические последовательности в вызове Move ничуть не замедляют работу программ.