Некоторые записи (структуры) в Windows имеют переменный размер, они начинаются с фиксированного заголовка, а далее следует переменная часть в виде массива. Когда объявляют такие структуры, они часто объявляются с массивом размера 1 на месте части переменного размера. Например:
type
PTOKEN_GROUPS = ^TOKEN_GROUPS;
_TOKEN_GROUPS = record
GroupCount: DWORD;
Groups: array [0..ANYSIZE_ARRAY - 1] of SID_AND_ATTRIBUTES;
end;
TOKEN_GROUPS = _TOKEN_GROUPS;
TTokenGroups = TOKEN_GROUPS;
PTokenGroups = PTOKEN_GROUPS;
Если вы посмотрите объявление ANYSIZE_ARRAY, то увидите, что эта константа равна 1, поэтому этот код объявляет запись с ведомым массивом из единственного элемента.
При таком объявлении, вам нужно выделять память для одной такой записи примерно так:
var
TokenGroups: PTokenGroups;
begin
GetMem(TokenGroups, SizeOf(TTokenGroups) + (NumberOfGroups - 1) * SizeOf(TSidAndAttributes));
и вы должны инициализировать запись примерно так:
TokenGroups.GroupCount := NumberOfGroups;
for Index := 0 to NumberOfGroups - 1 do
TokenGroups.Groups[Index] := ...;
Многие люди считают, что запись должна быть объявлена так:
type
PTOKEN_GROUPS = ^TOKEN_GROUPS;
_TOKEN_GROUPS = record
GroupCount: DWORD;
end;
TOKEN_GROUPS = _TOKEN_GROUPS;
TTokenGroups = TOKEN_GROUPS;
PTokenGroups = PTOKEN_GROUPS;
Тогда код выделения был бы таким:
var
TokenGroups: PTokenGroups;
begin
GetMem(TokenGroups, SizeOf(TTokenGroups) + NumberOfGroups * SizeOf(TSidAndAttributes));
Эта альтернатива имеет два недостатка. Один косметического плана, а другой - фатальный.
Во-первых, косметический недостаток: становится тяжелее получать доступ к элементам массива в переменной части. Например, инициализация теперь должна выглядеть примерно так:
TokenGroups.GroupCount := NumberOfGroups;
for Index := 0 to NumberOfGroups - 1 do
PSidAndAttributes(Cardinal(TokenGroups) + SizeOf(TTokenGroups) + Index * SizeOf(TSidAndAttributes))^ := ...;
Но настоящий недостаток является фатальным. Вышеприведённый код вылетает на 64-х битных Windows. Запись TSidAndAttributes выглядит вот так:
PSID_AND_ATTRIBUTES = ^SID_AND_ATTRIBUTES;
_SID_AND_ATTRIBUTES = record
Sid: PSID;
Attributes: DWORD;
end;
SID_AND_ATTRIBUTES = _SID_AND_ATTRIBUTES;
TSidAndAttributes = SID_AND_ATTRIBUTES;
PSidAndAttributes = PSID_AND_ATTRIBUTES;
Заметьте, что первое поле этой записи является указателем, PSID. Поэтому запись TSidAndAttributes требует выравнивания на границу указателя, который на 64-х битной Windows равен 8-ми байтам. С другой стороны, предлагаемая новая запись TTokenGroups состоит просто из одного DWORD-а, требуя поэтому только 4-х байтового выравнивания. SizeOf(TTokenGroups) равен четырём.
Я надеюсь, вы видите, к чему я клоню.
При предлагаемом объявлении записи, массив из записей TSidAndAttributes не будет размещён на границу по 8-и байтам, а только по 4-м. Необходимый пробел (padding) между полем GroupCount и первым элементом TSidAndAttributes отсутствует. Попытка доступа к элементу массива приведёт к вылету с исключением STATUS_DATATYPE_MISALIGNMENT.
Окей, вы можете спросить: почему бы тогда не использовать массивы нулевой длины, вместо массива с одним элементом?
Потому что путешествия во времени ещё не совершенны.
Массивы нулевой длины не были частью стандарта C до 1999-го года. Поскольку Windows существовала задолго до этого времени, то она не могла воспользоваться этой возможностью языка C (прим. пер.: ну а заголовочники Delphi являются просто буквальным переводом заголовочников Windows - чтобы C-ный код, переведённый на Delphi, работал бы без изменений).
Александр, а как в Delphi объявить массив нулевой длины? array [0..-1]?
ОтветитьУдалитьУ меня с наскоку не получилось, и я пока оставил [0..0] (по аналогии с Windows.pas), плюс добавил константу:
const
MyTypeHeaderSize = SizeOf(TMyTypeHeader) - SizeOf(Byte)
(Byte - потому что массив байтов) и память выделяю как-то так:
GetMem(MyTypeHeaderSize + ElementsCount)
но мне такое не очень нравится..
Может я не очень понятно выразился... т.е. я бы хотел в исходнике видеть что-то типа такого:
ОтветитьУдалитьTTokenGroups = record
GroupCount: DWORD;
Groups: array [0..-1] of SID_AND_ATTRIBUTES;
end;
и чтобы SizeOf(TTokenGroups) автоматически вычислялся бы как SizeOf(TTokenGroups) - SizeOf(TSidAndAttributes).
Мне кажется, это было бы и наглядно и понятно.
Насколько я знаю - никак.
ОтветитьУдалитьОбсуждение: http://www.delphikingdom.ru/asp/answer.asp?IDAnswer=80934
Замечательное обсуждение! :с)
ОтветитьУдалитьПремного благодарен за ссылку!