Что означает "Packed Now Force Byte Alignment of Records"? - программирование
Подтвердить что ты не робот

Что означает "Packed Now Force Byte Alignment of Records"?

Что нового для Delphi XE2 содержит ниже.

Packed Now Принудительное выравнивание байтов записей

Если у вас есть устаревший код, который использует тип упакованной записи, и вы хотите для связи с внешней DLL или с С++, вам нужно удалить слово "упакован" из вашего кода. Упакованное ключевое слово теперь приводит к выравниванию байтов, тогда как в прошлом это не обязательно делало это. Поведение изменение связано с изменениями совместимости выравнивания С++ в Delphi 2009.

Я не понимаю этого. Я борюсь с этим моментом: тогда как в прошлом это не обязательно делало это. То, что я не могу смириться, - это то, что упакованный всегда приводил к выравниванию записей в байтах, насколько мне известно. Может ли кто-нибудь привести пример упакованной записи, которая не выровнена по байтам? Очевидно, что это должно быть в более ранней версии.

Почему документы говорят "если вы хотите установить связь с внешней DLL или с С++, вам нужно удалить слово, упакованное из вашего кода"? Если внешний код использует #pragma pack(1), то что нам делать, если он упакован - это пределы?

Как насчет директивы $ALIGN? Являются ли {$A1} and {$A-} эквивалентными packed или есть какой-то дополнительный смысл с packed?

Кажется, что я что-то упускаю и буду признателен, если кто-нибудь сможет это объяснить. Или документация очень бедна?

Обновление

Я уверен, что документация относится к выравниванию самой записи, а не к макету записи. Здесь небольшая программа, которая показывает, что использование packed в записи заставляет выравнивание записи быть 1.

program PackedRecords;
{$APPTYPE CONSOLE}
type
  TPackedRecord = packed record
    I: Int64;
  end;

  TPackedContainer = record
    B: Byte;
    R: TPackedRecord;
  end;

  TRecord = record
    I: Int64;
  end;

  TContainer = record
    B: Byte;
    R: TRecord;
  end;

var
  pc: TPackedContainer;
  c: TContainer;

begin
  Writeln(NativeInt(@pc.R)-NativeInt(@pc.B));//outputs 1
  Writeln(NativeInt(@c.R)-NativeInt(@c.B));//outputs 8
  Readln;
end.

Это дает тот же результат на Delphi 6, 2010, XE и XE2 32 бит и XE 64 бит.

4b9b3361

Ответ 1

Последние обновления документации удалили весь текст, на котором был основан этот вопрос. Я пришел к выводу, что исходный текст был просто ошибкой документации.

Ответ 2

Поскольку я не являюсь компилятором Delphi, я также могу угадать, как это делали другие: выравнивание записи может не предназначаться для выравнивания элементов внутри записи, а сама запись.

Если вы объявляете переменную записи, она выровнена с некоторым адресом в памяти, который, скорее всего, выровнен по 4-байтовой границе. Это произошло (как было протестировано в D2007) для упакованных и распакованных записей.

Теперь в XE2 упакованная запись помещается в 1-байтовую границу, в то время как распакованные записи помещаются на некоторую четную границу, которую можно контролировать с помощью ключевого слова align. Вот так:

type
  TRecAligned = record
    b1: byte;
    u1: uint64;
  end align 16;

  TRecPackedAligned = packed record
    b1: byte;
    u1: uint64;
  end align 16;

Упакованная запись по-прежнему выравнивается по 1-байтовой границе, а распакованная запись выровнена с 16-байтовой границей.

Как я уже сказал, это всего лишь предположение. Формулировка цитаты Эмбаркадеро не так уж ясна в этом вопросе.

Ответ 3

Насколько я помню, record использовался для упаковки с версии компилятора вокруг Delphi 5-6.

Затем по соображениям производительности равные поля record были выровнены в соответствии с настройками в опции проекта. Если вы определяете packed record, в записи не будет никакого выравнивания.

Текст, который вы цитируете, связан не с XE2, а с изменением Delphi 2009. См. эту запись в блоге для исторической цели.

Я предполагаю, что "Packed" Now Bull Alignment of Records "относится к функции Delphi 2009 {$OLDTYPELAYOUT ON}, которая может иметь некоторые проблемы с реализацией перед XE2. Я согласен с вами: это звучит как проблема с документацией.

Ответ 4

pascal всегда поддерживает упакованные структуры, которые, вероятно, проще всего объяснить с помощью примера

Допустим, у вас есть два массива x и y, и мы используем 16-битный процессор. То есть размер "слова" процессора составляет 16 бит.

myrec = 
record
  b : byte;
  i : integer;
end;

x : array[1..10] of myrec;
y : packed array[1..10] of myrec;

Pascal только сообщает вам, что у вас есть 10 элементов для работы, каждый из которых содержит 3 байта информации (позволяет предположить, что integer является старым 16-битным разнообразием). Он фактически ничего не говорит о том, как хранится эта информация. Вы можете предположить, что массив хранится в 30 последовательных байтах, однако это не обязательно true и полностью зависит от компилятора (действительно хорошая причина избежать математики указателя).

Компилятор может помещать фиктивный байт между значениями полей b и i, чтобы гарантировать, что и b, и я попадают на границы слов. В этом случае структура будет содержать в общей сложности 40 байт.

"Упакованное" зарезервированное слово инструктирует компилятор оптимизировать размер по скорости, тогда как оставляя упакованную, компилятор обычно оптимизирует скорость по размеру. В этом случае структура будет оптимизирована и может занимать всего 30 байт.

Снова я говорю "может", потому что он все еще зависит от компилятора. "Упакованное" ключевое слово просто говорит, чтобы собрать данные ближе друг к другу, но на самом деле не говорит, как это сделать. Поэтому некоторые компиляторы просто игнорируют ключевое слово, тогда как другие используют упакованные для отключения выравнивания слов.

В последнем случае то, что обычно может произойти, состоит в том, что каждый элемент выравнивается по размеру слова процессора. Вы должны понимать здесь, что размер слова процессора обычно представляет собой размер регистра, а не просто "16 бит", как обычно это означает. В случае 32-битного процессора, например, размер слова составляет 32 бита (хотя мы обычно определяем слова как 16 бит - это просто исторический возврат) В некотором смысле это эквивалент "памяти" дискового сектора.

При упаковывании данных вы потенциально можете иметь такие значения, как целое пересечение границ слов, поэтому требуется больше манипуляции с памятью (так же, как нужно читать два сектора диска, если запись пересекает границу сектора).

Таким образом, в основном, что означает изменение, delphi теперь полностью использует упакованное ключевое слово и форсирует выравнивание байтов (над выравниванием слов) все время.

Надеюсь, этого достаточно, чтобы объяснить это. На самом деле это большая тема, на которую я могу разумно ответить в нескольких параграфах.


UPDATE, продолжение потока комментариев ниже...

DavidH > Я не могу себе представить, что вы имеете в виду, что компилятор способен производить разные выходные данные при идентичном вводе

Да, это именно то, что я говорю. Не обязательно от одной компиляции к следующей на одном компиляторе, но, конечно, между версиями компилятора, брендов компилятора и разных платформ.

TonyH > Я думаю, что большинство из нас всегда считалось, что упакованный = байт выровнен, так что теперь мы царапаем головы для сценария, где это не так. @Дэвид Хеффернан спрашивает, знает ли кто-нибудь

Тони, ты ударил ноготь по голове здесь. Вы предположили, что упакованные работы похожи на директиву компилятора, которая определяет, как организована память данных. Это не то, что это значит. Лучшей аналогией является рассмотрение директивы optimize для генерации кода. Вы знаете, что код оптимизирован, но не обязательно, каков будет исходный выводный код, и вам не нужно.

Я могу привести много примеров. Допустим, у вас есть следующее (я использую массив, он может так же легко записываться в запись)

myarray = packed array[1..8] of boolean

Если вы затем напечатаете SizeOf (myarray), что бы вы ожидали? Ответ заключается в том, что delphi не гарантирует, что упакованный реализован каким-либо конкретным способом. Поэтому ответ может быть 8 байтов (если вы байт выравниваете каждый элемент). Для Delphi так же корректно упаковывать их как биты, и поэтому весь массив может вписываться в 1 байт. Это может быть 16 байт, если компилятор решает не оптимизировать границы байтов и работает на 16-битной архитектуре. Да, это было бы менее эффективно с точки зрения скорости, но весь смысл упаковки - оптимизировать размер по скорости. Независимо от того, что он делает, невидимо для разработчика, поскольку они просто обращаются к элементам массива и не делают никаких предположений и не пытаются использовать свою собственную математику указателей для доступа к базовым данным.

Точно так же вы не можете даже гарантировать внутреннюю организацию примитива - он зависит от платформы, а не от компилятора. Например. Если вы работаете с несколькими архитектурами, вы будете знать о проблемах, связанных с тем, сколько бит составляет целое число; независимо от того, хранятся ли байты внутри этого целого в байтовом порядке или в обратном порядке байта.

Эти типы архитектурных проблем именно поэтому разработаны различные методы, такие как файл с разделителями-запятыми, xml и т.д. Для обеспечения надежной общей платформы.

Подводя итог, Дэвид говорит, что вы действительно задаете неправильный вопрос. Если вы рассмотрите цитату Embarcadero в контексте того, что я сказал, вы увидите, что она согласуется с тем, что я говорю.