22 апреля 2011 г.

Ответ на задачку №7

Ответ на задачку №7.

Это была не очень сложная задачка на умение пользоваться справкой. В частности, описании выравнивания встроенных типов данных можно увидеть такие слова (выделение моё):
If two fields share a common type specification, they are packed even if the declaration does not include the packed modifier and the record type is not declared in the {$A-} state. Thus, for example, given the following declaration:
type
  TMyRecord = record
    A, B: Extended;
    C: Extended;
  end;
A and B are packed (aligned on byte boundaries) because they share the same type specification. The compiler pads the structure with unused bytes to ensure that C appears on a quadword boundary.
Иными словами - есть небольшая разница. А именно: если у нас неупакованная запись, то вторая запись будет занимать меньше места, чем первая, из-за упаковки первых двух полей.

К примеру:
{$A8}

type
  TMyRecord1 = record
    A: Extended;
    B: Extended;
    C: Extended;
  end;

  TMyRecord2 = record
    A, B: Extended;
    C: Extended;
  end;
Размер первой записи будет 48 байт - потому что каждый 10-ти байтный Extended выравнивается до 16-ти байт (16-ть байт - это ближайшее число, большее 10 байт и кратное 8-ми). Кроме того, размер записи округляется вверх до размера, кратного 8-ми.

Размер второй записи будет 40 байт - потому что первые два 10-ти байтных Extended не выравнивается (т.е. они упакованы или выровнены на байтовую границу). А последний Extended выравнивается как обычно - т.е. начинается на 24-х байтах. Ну и снова: запись округляется вверх до 8-ми кратной границы, что есть 40.

P.S. Как уже заметили в комментариях, этот код не работает как ожидается в Delphi XE (а также в Delphi 2010 и Delphi 2009) - выводя 48 для обоих случаев. В Delphi 2007 и ниже всё работает, как и указано в документации - 48 и 40. Я отправил отчёт о несогласованности в Quality Central (ссылки нет, т.к. я отправлял отчёт на бету - больше шансов, что исправят).

P.P.S. Как оказалось - это не баг компилятора, а документации. Она не описывает изменение в последних версиях Delphi, введённые для совместимости с C++. В частности, старое поведение типов, про которое указано в документации сейчас, включается директивой {$OLDTYPELAYOUT ON}.

9 комментариев :

  1. Ужас, слава богу, убрали эту кривизну в новых версиях :)

    ОтветитьУдалить
  2. Да уж, такой подводный камень лучше в самом деле ликвидировать.

    ОтветитьУдалить
  3. Может и лучше, но...

    1. Что делать с уже написанным и работающим кодом, который вдруг перестанет работать? (и попробуй потом найти причину, а, главное, ещё и исправить)

    2. Если это и менять, то и документацию - менять тоже.

    И так и сяк - плохо.

    ОтветитьУдалить
  4. Здравствуйте. Вопрос не совсем по теме по задачке, но близок.
    Вы не знаете почему в 64 разрядных Дельфи убрали тип Extended?
    Везде куча предположений, но нигде не нашёл точного ответа.
    PS
    Они этим "эффективным менеджментом" выстрелили себе в ногу, полностью перекрыв для некоторых приложений переход на 64бит версии.
    Вопрос, зачем??

    ОтветитьУдалить
    Ответы
    1. Это не совсем от разработчиков Delphi зависит. Это сделано потому что Extended - это аппаратный тип для сопроцессора x87. В x86-64 сопроцессор x87 не используется, а числа с плавающей запятой реализуются не сопроцессором, а самим центральным процессором - через SSE инструкции. Там такого типа нет, 8-ми байтовый Double - это максимум. Это требования единой модели вызова x86-64.

      P.S. Надо ещё заметить, что сам по себе тип Extended - это особенность Delphi. Во многих языках есть только Single и Double даже на x86-32.

      P.P.S. Да, в x86-64 всё ещё можно задействовать сопроцессор x87 - на ассемблере. Но тут нужно понимать, что если раньше (в x86-32) все числа с плавающей запятой (single/double/extended) реализовывались только внутри сопроцессора x87, то теперь Single и Double должны быть в регистрах XMM (SSE инструкции), а Extended, если вы захотите его использовать, - в регистрах x87. Т.е. смешанные вычисления уже просто так не сделаешь, нужно гонять данные между двумя процессорами.

      Удалить
    2. "Это требования единой модели вызова x86-64." - А вот это и непонятно.
      В 32 разрядных Дельфи в той же 64 разрядной Windows всё работает с типом Extended. И никакая модель вызова не мешает. Почему же такие проблемы возникают в 64 разрядной Дельфи?
      Т.е. это ограничение Windows?? Но зачем они это сделали?? Они, вроде, партнёры Интел. Взяли и обрубили их FPU. А SSE - инвалид по сравнению с FPU. Медленно , неудобно и неточно.
      И ещё, а вы не знаете в Линукс 64 бит та же история c Extended?
      Например, Лазарус64 будет работать в Линукс64 с типом Extended?

      Удалить
    3. Потому что в 32-х разрядных Delphi (и не только Delphi) модель вызова диктует, что для передачи чисел с плавающей запятой используются регистры сопроцессора x87: "A real parameter is always passed on the stack. Real results are returned in the floating-point coprocessor's top-of-stack register (ST(0))".

      В противоположность этому, модель вызова x86-64 диктует, что для передачи чисел с плавающей запятой используются регистры SSE: "The first four arguments are placed onto the registers. That means ... XMM0, XMM1, XMM2, XMM3 for floating point arguments. Floating point return values are returned in XMM0". Никакого x87 здесь не предусмотрено.

      В Linux (и вообще, большинстве Unix-систем, включая MacOS) ситуация полностью аналогичная. Конечно, там используется своё соглашение вызова, но правила там практически те же: "While XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for the first floating point arguments. Floating-point return values are similarly stored in XMM0 and XMM1".

      Удалить
    4. Что означает: модель диктует? В 32 разрядных почему-то этого нет. Зачем передавать числа с плавающей точкой через SSE, когда для этого есть FPU?
      Какое отношение имеет передача чисел SSE к отсутствию поддержки Extended? Ведь поддержка double в FPU есть.
      Вы можете привести ссылки на статьи в которых детально расписывается и объясняется эта проблема и как это работает изнутри?

      Удалить
    5. P.S.
      Например, я в dll написал на ассемблере процедуру , которая получает и передаёт два числа типа Extended через ST(0), ST(1)
      Если я скомпилирую это в 64 разрядной Дельфи и буду использовать эту dll в программе, скомпилированной в 64 разрядном GCC, она будет работать в 64 Windows?
      Или та же самая процедура, но с передачей чисел Extended через общий стек:
      fstp tbyte prt [esp],
      fstp tbyte prt [esp-10]

      Если не будет работать, то почему? Если будет работать, то почему 64-компиляторы не используют Extended?

      P.P.S.
      Мои подобные 32 разрядные dll и exe работают в Windows10x64 без ошибок.
      Но у меня нет сейчас возможности проверить это на 64 компиляторах. :)

      Удалить

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

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

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

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

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

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