В этой статье я хотел бы рассказать и показать особенности использования
"уплотнения сложных типов данных" в Delphi.

Все когда-нибудь имели дело с записями. Записи используются во многих случаях
– для создания файловых справочников, удобной группировки и представления
данных, в системном программировании и т.д. Например, открыв файл Windows.pas
(стандартный модуль, подключается в разделе uses), можно найти что-то типа
такого описания:

_POINTL = packed record
x: Longint;
y: Longint;
end;

или

TISHMisc = packed record
case Integer of //Здесь используется запись с вариантами
0: (PhysicalAddress: DWORD);
1: (VirtualSize: DWORD);
end;

Что же здесь обозначает ключевое слово packed? Слово Packed говорит Delphi
минимизировать память. Так что же получается, без этого слова у нас структура
занимает памяти больше?

Вот пример записи:

TRecord = Record
pole1 : byte;
pole2 : string[4];
pole3 : integer;
pole4 : Int64;
end;

Давайте подсчитаем ее размер: pole1 – 1 байт, pole2 – 5 байт (надеюсь, не
забыли 4 байта под символы и 1 под размер), pole3 – 4 байта, pole4 – 8 байт.
Если их сложить получиться 18 байт. Давайте проверим:

size1 := sizeof(TRecord);
ShowMessage(‘Размер обычной записи = ’+IntToStr(size1));

Результат: 24.

Удивлены? А теперь добавьте слово packed перед словом Record… Размер 18 как и
подсчитали ранее. Так куда же у нас пропали целые 6 байт?! А если у нас будет
массив из 10 000 000 таких записей, например в каком-нибудь справочнике, мы
потеряем около 57 Мегабайт?

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

procedure TForm1.Button1Click(Sender: TObject);
type
// обычная запись
TRecord = Record
pole1 : byte;
pole2 : string[4];
pole3 : integer;
pole4 : Int64;
end;
// упакованная записи
TPackedRecord = Packed record
pole1 : byte;
pole2 : string[4];
pole3 : integer;
pole4 : Int64;
end;

var
Rec : TRecord;
packedRec : TPackedRecord;
size1 : integer;
begin
ShowMessageFmt('Adress = %P',[Addr(Rec)]); //Вывод адреса нашей записи
size1 := sizeof(Rec); //Узнаем размер
// присваиваем некоторые данные нашей записи
Rec.pole1 := $FF;
Rec.pole2 :='hack';
Rec.pole3 := $AAAAAAAA;
Rec.pole4 := $1122334455667788;
ShowMessage('Размер структуры = ' + IntToStr(size1)); //выводим размер
end;

Функцию ShowMessageFmt используем для того, чтобы вывести адрес нашей
структуры. Обычный ShowMessage не поможет, т.к. нам надо вывести указатель (тип
Pointer). Этот адрес меняется, так что у вас может быть другой. Функция Addr –
это получение адреса, возвращаемый тип Pointer. Вот ее результат:

Запомним этот адрес. Теперь поставим breakpoint на последнем ShowMessage –
нажмем F5 на этой сроке.

Теперь запустим. Программа выдаст сообщение с адресом, а затем остановиться
на предпоследней строке. Нажимаем Ctrl + Alt + C. Появится окно дизассемблера.
Щелкаем правой кнопкой мыши по левой-нижней области (dump памяти) и выбираем
"Идти к адресу".

Вводим наш адрес записи (например так: $0012F568), который получили с помощью
ShowMessageFmt, и попадаем на адрес начала нашей структуры.

Помним, что pole1 мы присвоили значения $FF (т.е. =255), pole2 =’hack’, pole3
= $AAAAAAAA (4 байта, т.е. =2863311530) и т.д. Посмотрим на рисунок, на нем я
отобразил структуру байтов и как они расположены в памяти.

Как видим, после данных pole2 идут байты выравнивания, которые выравнивают
границу памяти до 8 байт. И поэтому Pole3 начинается с адреса кратному 8. То же
самое видим и с Pole4 – сначала выравнивается граница (байты 0E 00 00 00), а
затем идут сами данные (хочу заметить, что в памяти данные хранятся в обратном
порядке, что видно в Pole4).

А теперь проверим упакованную запись. Переменную Rec заменяем в коде на
packedRec, т.е. используем packed record. И проделываем все то же самое:

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

Возможно возник вопрос: почему в первом примере данные выравнивались по
границе 8 байт? Все дело в настройках компилятора. Если вы откроете Project –
Option – Compiler (опции компилятора), то там будет списочек Record field
alignment, в котором можно выбрать нужное выравнивание (8,4,2 или 1 байт). Не
забываем перекомпоновать проект после изменений. Кстати выравнивание по 1 байту
и есть упаковка данных.

Второй вопрос: почему же всегда не упаковывать данные? К сожалению, упаковка
данных имеет не только плюсы, но и минусы. Так, например, не выравнивая границу
мы увеличиваем время доступа к полям. Поэтому использовать упаковку надо с умом.

Кстати, в том же файле Windows.pas можно найти не только упаковка записей, но
и массивов, вот пример:

CprMask: packed array[0..3] of DWORD;

Попробуйте сами проанализировать упаковку этого массива.

Оставить мнение

Check Also

LUKS container vs Border Patrol Agent. Как уберечь свои данные, пересекая границу

Не секрет, что если ты собрался посетить такие страны как США или Великобританию то, прежд…