Выравнивание и упакованные записи
Современные процессоры устроены таким образом, что они считывают данные отдельными порциями по 4 байта. Кроме того, эти порции всегда выравниваются по границе двойного слова. Это означает, что если данное «переходит» границу 4 байта, то процессору придется выдать две команды на считывание памяти: первая команда на считывание первой части данного, а вторая - на считывание второй части. Затем процессор соединит две части и отбросит ненужные биты.
Все глобальные и локальные примитивные данные выравниваются должным образом. Данное типа Record компилятор Delphi выравнивает автоматически. Для этого он добавляет незначащие (пустые) байты. Пусть, например, объявлен следующий тип:
TRec = Record
ValByte: Byte;
ValLongWord: LongWord;
End;
и переменная Rec этого типа. На первый взгляд применение функции sizeof к типу TRec (или к переменной Rec) даст размер 5 байт. Однако верным ответом будет 8 байт. Дело в том, что компилятор вставит между полями ValByte и ValLongWord три дополнительных байта, чтобы выровнять поле ValLongWord по 4-хбайтовой границе. Адреса записи Rec и ее первого поля ValByte будут совпадать, а адрес поля ValLongWord будет больше адреса ValByte на 4.
Если тип объявить следующим образом:
TRec = packed Record
ValByte: Byte;
ValLongWord: LongWord;
End;
то функция SizeOf(TRec) даст результат 5. Но в этом случае доступ к полю ValLongWord потребует больше времени, чем в предыдущем примере, поскольку граница 4 байт будет «пересекать» слот этого поля. Следовательно, на практике желательно пользоваться следующей рекомендацией: если используется ключевое слово packed, то сначала целесообразно объявить 4‑байтные поля, или поля, размер которых кратен 4, а потом уже все остальные.
Выравнивание данных помогает выполнять диспетчер распределения памяти Delphi. Он выравнивает не только 4‑байтные значения по границе 4 байт, но 8-байтные по границе 8 байт. Это имеет большое значение для вещественных переменных: операции с числами с плавающей точкой выполняются быстрее, если переменные выровнены по границе 8 байт.
Записи с вариантной частью
Иногда в записях в зависимости от ситуации могут присутствовать различные наборы полей или поля могут иметь различные типы. Было бы нерационально отводить место под все возможные поля, если все они одновременно не используются. В этих случаях можно вводить в запись вариантную часть, в которой перечисляются варианты полей и условия их использования.
Вариантная часть оформляется как оператор множественного выбора Case, который размещается в описании типа записи после объявления обязательных полей (т. е. полей, присутствующих при любых условиях). Вариантная часть начинается ключевым словом Case. Тег (tag - ярлык, признак) - любой допустимый идентификатор. Его указание не обязательно. Если тег указывается, то в запись включается соответствующее ему поле, по значению которого можно узнать, какой именно вариант полей используется. При отсутствии тега не записывается и двоеточие после него. Приведем общую форму описания записи с вариантной частью.
Type <имя типа> = Record <список имен обязательных полей>: <тип 1>; × × × <список имен обязательных полей>: <тип m>; Case <тег> : <порядковый тип> Of <список значений 1>: (<вариант 1>); × × × <список значений n>: (<вариант n>); End; | |
Вариантная часть |
После тега или сразу после слова Case, если тег отсутствует, указывается порядковый тип. В случае, когда тег указан, этот тип является типом поля тега. Если тега нет, то это просто тип значений, которые будут перечисляться ниже.
В части <список значений> указывается одно или несколько значений заданного порядкового типа (типа тега). А <вариант>, указываемый в круглых скобках, - это разделенный символами «;» список объявлений вариантных полей, не входящих в списки обязательных полей. Каждое такое объявление имеет вид:
<список имен полей> : <тип>;
Указываемые типы полей не могут быть длинными строками, динамическими массивами, интерфейсами, Variant или структурами, содержащими эти запрещенные типы. Но тип поля может быть указателем на подобные типы.
Объявленные варианты полей занимают в памяти один и тот же участок. Компилятор отводит слот для всей записи, ориентируясь на самый большой по размеру вариант. Все обязательные поля, поле тега и все поля всех вариантов доступны программе в любой момент времени, независимо от значения тега. Программа может в любой момент занести в запись значение любого поля любого варианта. Но если после этого заносится значение поля другого варианта, оно может изменить значение предыдущего варианта. Для контроля таких изменений предназначен тег. Например, пусть имеются следующие объявления:
Type
TRecVar = Record
Flag: Boolean;
longField: LongInt;
Case logVal: Boolean Of
True: (X1, Y1: Real; intField: Integer);
False:(A1: Real; ch1, ch2: Char);
End;
Var RecVar: TRecVar;
Адрес всей переменной RecVar совпадает с адресом ее поля Flag, допустим, этот адрес равен $41. Несмотря на то, что поле Flag занимает один байт, адрес поля longField будет на 4 больше за счет выравнивания, т. е. он будет равен $45. Поля Х1 и А1 будут занимать один и тот же слот размером 8 байт. Поэтому если программа присвоит некоторое значение полю Х1, то это же значение будет наблюдаться в поле А1. Вывод: чтобы избежать ошибок, связанных с интерпретацией содержимого различных полей, необходимо внимательно контролировать значение тега.
Дата добавления: 2015-08-21; просмотров: 1039;