вторник, 23 июня 2009 г.

Почему некоторые записи оканчиваются массивом размером 1?

Это перевод Why do some structures end with an array of size 1? Автор: Реймонд Чен.

Некоторые записи (структуры) в 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, работал бы без изменений).

4 комментария:

  1. Александр, а как в Delphi объявить массив нулевой длины? array [0..-1]?
    У меня с наскоку не получилось, и я пока оставил [0..0] (по аналогии с Windows.pas), плюс добавил константу:
    const
    MyTypeHeaderSize = SizeOf(TMyTypeHeader) - SizeOf(Byte)
    (Byte - потому что массив байтов) и память выделяю как-то так:
    GetMem(MyTypeHeaderSize + ElementsCount)
    но мне такое не очень нравится..

    ОтветитьУдалить
  2. Может я не очень понятно выразился... т.е. я бы хотел в исходнике видеть что-то типа такого:

    TTokenGroups = record
    GroupCount: DWORD;
    Groups: array [0..-1] of SID_AND_ATTRIBUTES;
    end;
    и чтобы SizeOf(TTokenGroups) автоматически вычислялся бы как SizeOf(TTokenGroups) - SizeOf(TSidAndAttributes).

    Мне кажется, это было бы и наглядно и понятно.

    ОтветитьУдалить
  3. Насколько я знаю - никак.

    Обсуждение: http://www.delphikingdom.ru/asp/answer.asp?IDAnswer=80934

    ОтветитьУдалить
  4. Замечательное обсуждение! :с)
    Премного благодарен за ссылку!

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

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