Упаковка складних типів даних в delphi

У цій статті я хотів би розповісти і показати особливості використання
"Ущільнення складних типів даних" в 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));

Здивовані? А тепер додайте слово 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;

Пам'ятаємо, що pole1 ми присвоїли значення $ FF (тобто = 255), pole2 = 'hack', pole3
= $ AAAAAAAA (4 байта, тобто = 2863311530) і т.д. Подивимося на малюнок, на ньому я
відобразив структуру байтів і як вони розташовані в пам'яті.

А тепер перевіримо упаковану запис. Змінну 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;

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

Покажи цю статтю друзям: