Создаем дополнительную память на диске с помощью Delphi

 | 15.54

Мой Компьютер, №04 (435), 15.01.2007

О файлах и памяти

Многие не знают (я и сам не знал, пока мне не рассказали) об одной возможности при написании программ. Она очевидна, но пока не возникает потребность, никто об этом не задумывается. Я имею ввиду работу в файле «без типа». Суть в том, что наши объемные данные мы записываем в этот самый файл на винте, а не в оперативку. Если надо изменить какую-то часть, то читаем в память, изменяем и записываем обратно. Мы создаем как бы свою личную маленькую оперативную память. PhotoShop, например, так поступает. Но эту технологию оправданно применять лишь в том случае, если вам мало всей оперативной памяти, ведь винчестер работает во много раз медленнее оперативной памяти. Да и программировать становится труднее, ведь в случае с оперативкой мы не заботимся о выделении/освобождении динамической памяти — GetMem, FreeMem и все. В случае же файла нам придется писать эти процедуры самим, создавать список дыр (свободных участков внутри файла), процедуры обслуживания этого списка. Создавать, в общем, механизмы управления памятью, которые уже реализованы в ОС.

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

Поясню на примере.

Чтобы записать в файл значение какой-то переменной, нам кроме этой переменной ничего больше не нужно, ведь ее размер можно узнать с помощью функции SizeOf. Например, если a: integer, f: file, то BlockWrite(f, a, SizeOf(a)). Чтобы записать некоторый объект в памяти, нужно иметь указатель на этот объект и его размер:

p: pointer;

size: integer;

BlockWrite(f, p^, size)

Переменную a тоже можно записать с помощью указателя на нее:

p := @a;

size := SizeOf(a);

BlockWrite(f, p^, size)

Мне надо было написать универсальную процедуру записи в файл объектов (под словом объект я имею ввиду не тот объект, который основа ООП, а просто некоторую запись, например, как объявленную ниже):

type

TSomeType = record

   key, size: integer; //уникальное ключевое поле и размер

   //другие поля

   x: array[1..8] of byte

   end;

var b: TSomeType

Конечно, можно написать процедуру, в которую надо передавать адрес, ключевое слово (необходимо для сортировки) и размер. Но ведь эти данные хранятся в самой записи (первые и вторые четыре байта), так что мы будем передавать лишнее. Одним словом, процедуре надо передавать только адрес. Ключевое поле и размер она должна сама суметь прочитать. С ключевым полем все понятно, адрес на объект указывает прямо на него, поэтому легко можно прочитать так: prockey := integer(p). С полем размера тоже было бы аналогично, если бы нам сдвинуть указатель вверх на четыре байта (говоря «вверх», я имею ввиду «в сторону возрастания адресов»). Так что налицо необходимость адресной арифметики. Но Делфи рулит, поэтому можно обойтись без нее!

Адресная арифметика без изменения адресов

Сейчас я покажу, как можно прочитать вторые четыре байта, если указатель указывает на первые. Объявим еще такое:

type

 TArray12 = array[1..2] of integer;

var

 m: TArray12;

Повесьте где-то на ButtonClick, чтобы проверить работоспособность.

//заполняем поля key и size

b.key := 512;

b.size := 1024;

//формируем указатель на эту запись

p := @b;

//читаем первые восемь байт в массив

m := TArray12(p^);

// и выводим второй элемент массива — поле size

ShowMessage(IntToStr(m[2]));

Делфи рулит! Развить эту идею не составляет труда: используем массив не integer, а byte, получаем доступ к отдельным байтам, и так дальше. Как видите, без адресной арифметики вполне можно обойтись, но все же утрем сишникам нос и покажем, что в Делфи можно проводить те же операции с адресами.

Вариант 1. Асм

Пусть у нас есть переменная-указатель

p: pointer

Маленький пример кода, с помощью которого можно делать с этим указателем что угодно:

asm

 push eax

 //сохраним eax на всякий случай

 mov eax, p

 //заносим в eax значение нашего указателя

 //и делаем с ним что-нибудь, например, увеличиваем на 4

 add eax, 4

 //по сути, мы сдвинули указатель на 4 байта в сторону увеличения адресов

 mov p, eax

 //записываем новое значение указателя обратно в память

 //и восстанавливаем eax

 pop eax

end; //asm

Все просто! Теперь вместо add подставляете то, что нужно вам — и юзаете на здоровье! Приведу еще пример сложения двух указателей p1 и p2 в третий p:

asm

 push eax

 push ebx

 //сохраняем изменяемые регистры

 mov eax, p1

 mov ebx, p2

 //заносим в регистры значения слагаемых указателей

 add eax, ebx

 //суммируем и записываем в третий указатель

 mov p, eax

 pop ebx

 pop eax

 //восстановили регистры

end; //asm

Ассемблер рулит! Вот только сишники будут говорить, что ассемблер и Делфи — разные вещи, так что реализованная выше адресная арифметика — заслуга совсем не Делфи. Что ж, оставим асм в покое и реализуем первый пример чисто на Делфи.

Вариант 2. Без асма

Для этого нам потребуется еще пара переменных — a: LongInt (4 байта, целое, беззнаковое) и указатель на переменные типа LongInt: pInt: ^LongInt.

pint := @p;

//получили адрес в памяти, где хранится значение нашего указателя

a := pint^;

//прочитали это значение

a := a + 4;

//изменили

pInt^ := a;

//и записали обратно

Вышло даже на целую строчку короче!

Так что если вы собрались переходить на Си из-за того, что не нашли в Делфи аналогичных возможностей работы с адресами, то не спешите, а лучше пошевелите мозгами, ибо Delphi — the best!

Ярик Уланович aka Mahpella

Robo User
Web-droid editor

Добавить комментарий

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