“现在打包强制记录的字节对齐”是啥意思?

Posted

技术标签:

【中文标题】“现在打包强制记录的字节对齐”是啥意思?【英文标题】:What does "Packed Now Forces Byte Alignment of Records" mean?“现在打包强制记录的字节对齐”是什么意思? 【发布时间】:2012-01-17 15:40:04 【问题描述】:

Delphi XE2 的新增功能包含 following。

Packed Now 强制记录的字节对齐

如果您有使用打包记录类型的旧代码并且您希望 要与外部 DLL 或 C++ 链接,您需要删除单词 从您的代码中“打包”。 packed 关键字现在强制字节对齐, 而在过去,它不一定会这样做。行为 更改与 Delphi 中的 C++ 对齐兼容性更改有关 2009.

我不明白这一点。我正在为这一点苦苦挣扎:而在过去,它不一定会这样做。我无法调和的是,据我所知,packed 总是导致记录的字节对齐。谁能举一个非字节对齐的打包记录的例子?显然,这必须是早期版本。

为什么文档说“如果你想与外部 DLL 或 C++ 链接,你需要从你的代码中删除打包这个词”?如果外部代码使用#pragma pack(1),那么如果packed是禁区怎么办?

$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 位上产生相同的输出。

【问题讨论】:

请记住,有两种对齐方式在起作用:记录中字段的对齐方式(这是打包的影响)和记录本身的对齐方式,例如,在这些记录的数组中.从历史上看,我相信打包并不会影响记录本身的对齐方式,后来改变了,或者反过来。 绝望的谷歌搜索揭示了你的问题和它提到的文章。我也不明白,我只是举个例子。打包记录中的未打包数组,或者可能是变体记录?目前在我的 linux 机器上,否则我会对实验产生足够的兴趣。 @dthorpe 嗨,丹尼。我知道布局和对齐之间的区别。现在的 Delphi 文档记录了打包记录的对齐方式为 1。但我的经验是,这一直是这样。在 D6 中肯定是这种情况。所以打包确实会影响布局和对齐。你是说如果你走得足够远,即 D1 说,那只包装受影响的布局? 我记得这是 Kylix 中的一个问题,需要进行大量内部讨论,但我不记得事情进展的细节。 【参考方案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 字节边界对齐。

正如我所说,这只是一个猜测。 Embarcadero 引述的措辞在主题上不是很清楚。

【讨论】:

我很确定更改涉及记录的对齐,而不是其内部布局。但是,打包记录在以前的版本中也有 1 对齐。我必须说,我不熟悉 align 语法。是最近的吗? 相关文档在这里:docwiki.embarcadero.com/RADStudio/en/… 我偶然发现了它,但找不到任何关于它的文档。我不确定您的假设是否正确:在线帮助提到 $A 指令控制 Delphi 记录类型和类结构中字段的对齐,而上面的引用说 记录对齐我> 我们都知道文档有多么不稳定。通过反复试验可以发现,packed 和 $A- 和 $A1 都导致记录的对齐为 1。 如果我们要认真对待 QC 网站,此行为将被视为 defect。【参考方案3】:

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 个字节的信息(假设整数是旧的 16 位变体)。 它实际上并没有说明这些信息是如何存储的。 您可以假设数组存储在 30 个连续字节中,但这不是必须的,并且完全依赖于编译器(避免使用指针数学的一个非常好的理由)。

编译器可能会在字段值 b 和 i 之间放置一个虚拟字节,以确保 b 和 i 都落在字边界上。 在这种情况下,该结构总共需要 40 个字节。

'packed' 保留字指示编译器针对大小而不是速度进行优化,而如果不打包,编译器通常会针对速度而不是大小进行优化。 在这种情况下,结构将被优化,并且可能只占用 30 个字节。

我再次说“可以”,因为它仍然依赖于编译器。 'packed' 关键字只是说将数据更紧密地打包在一起,但实际上并没有说明如何。 因此,一些编译器简单地忽略了关键字,而另一些编译器使用打包来禁用字对齐。

在后者的情况下,通常可能发生的情况是每个元素都与处理器的字长对齐。 您需要在这里意识到,处理器的字长通常是寄存器大小,而不是通常所说的“16 位”。例如,在 32 位处理器的情况下,字长为 32 位(尽管我们通常将字定义为 16 位——这只是历史上的倒退) 从某种意义上说,它相当于磁盘扇区的“内存”。

通过打包数据,您可能具有跨越字边界的整数等值,因此需要更多的内存操作(很像如果记录跨越扇区边界则需要读取两个磁盘扇区)。

所以基本上这个变化的意思是,delphi 现在完全使用了 packed 关键字并一直强制字节对齐(而不是字对齐)。

希望这足以解释它。这实际上是一个更大的话题,我可以在几段中合理地回答。


更新,下面的评论继续......

DavidH>我无法想象你的意思是编译器能够在给定相同输入的情况下产生不同的输出

是的,大卫,这正是我要说的。不一定在同一个编译器上从一个编译到另一个编译,但肯定是在编译器版本、编译器品牌和不同平台之间。

TonyH > 我认为我们中的大多数人总是假设打包 = 字节对齐,所以现在我们正在为一个它没有的场景挠头。 @David Heffernan 询问是否有人认识

托尼,你在这里一针见血。 您假设打包的工作方式类似于指定数据存储器的组织方式的编译器指令。 这不是它的意思。一个更好的类比是考虑代码生成的优化指令。 您知道代码正在被优化,但您不一定要具体说明底层输出的代码是什么,也不需要。

我可以给你很多例子。 假设您有以下内容(我使用的是数组,它可以很容易地应用于记录)

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

如果你打印一个 SizeOf(myarray) 你会期待什么? 答案是 delphi 不保证以任何特定方式实现打包。 因此,答案可能是 8 个字节(如果您对每个元素进行字节对齐)。 delphi 将它们打包为位同样有效,因此整个数组可以放入 1 个字节。 如果编译器决定不对字节边界进行优化并且在 16 位架构上运行,它可能是 16 字节。 是的,它在速度方面的效率会降低,但打包的重点是优化尺寸而不是速度。 只要开发人员只是简单地访问数组元素并且不做任何假设并尝试自己的指针数学来访问底层数据,无论它做什么,开发人员都是不可见的。

同样,您甚至不能保证原语的内部组织——它依赖于平台,而不是依赖于编译器。例如。如果您曾在多种架构中工作过,您将意识到围绕多少位构成整数的问题;该整数中的字节是按字节顺序存储的,还是按反向字节顺序存储的。

这些类型的架构问题正是开发逗号分隔文件、xml 等各种技术的原因。为了提供一个可靠的通用平台。

总而言之,大卫,问题在于您实际上问错了问题。如果您在我所说的上下文中考虑 Embarcadero 的引用,您会发现它与我所说的一致。

【讨论】:

从未见过packed array[1..8] of boolean被任何版本的Delphi编译器打包成一个byte。 FreePascal 可以做到这一点——这对于像 ARM 这样具有一些 KB RAM 的嵌入式目标特别有用。但是我从来没有见过 Delphi 编译器做这种位打包,这在 C 编译器中很常见。 @Arnaud 对于这种 FPC 输出,@myarray[1] 会返回什么?我也不确定你对 C 的意思。在 C 标准中,明确规定数组的每个元素都必须是可寻址的,因此不能对 C 数组进行位打包。 @DavidHeffernan fpc 给出“错误:地址不能用于位压缩数组元素和记录字段”(除了每个位占用整个字节的模式),是的,打包可以' t 在 C 数组中完成,但可以使用 C 位域: struct unsigned bit1 : 1;无符号位2:1;在这样的结构中,你也不能取位域的地址。 @hvd 我不知道 FPC 中的打包数组,很高兴知道谢谢。但是由于我使用的是 Delphi 而不是 FPC,所以我只需要将这些知识存储在储物柜中以备将来使用。 关于 FPC 位压缩数组,RTFM - 请参阅 freepascal.org/docs-html/ref/refse14.html#QQ2-39-51 和 freepascal.org/docs-html/ref/refsu15.html#x39-460003.3.1 对于 FPC,@myArray[1] 可能会返回与 @myArray[0] 相同的结果,或者永远不会编译(我的猜测) - 但你永远不会使用@myArray[1],而是使用 myArray[1] 直接获取或设置布尔值。 Delphi 缺少打包/解包功能。【参考方案4】:

据我所知,record 曾经是从 Delphi 5-6 左右的编译器版本开始打包的。

然后,出于性能原因,根据项目选项中的设置对齐了普通的 record 字段。如果您定义 packed record,则记录中不会有任何对齐。

您引用的文本似乎与 XE2 无关,而是与 Delphi 2009 的更改有关。请参阅this Blog entry 了解历史目的。

我猜“'Packed' Now Forces Byte Alignment of Records”指的是 Delphi 2009 $OLDTYPELAYOUT ON 功能 - 在 XE2 之前可能存在一些实现问题。我同意你的看法:这听起来像是文档问题。

【讨论】:

我对 XE2 What's New 的理解是它与记录本身的 alignment 相关,而不是 $OLDTYPELAYOUT 的记录的 layout控制。我最好的猜测是,Internal Data Formats 的新增功能中的链接是在预期将内容添加到内部数据格式但尚未添加该内容时创建的。但我很可能是错的。这对我来说仍然是一个完全的谜! 关于$Align$A1$A- 对于所有版本的Delphi(包括XE2)都相当于packed——据我所知,我用这个编译器编译的代码.

以上是关于“现在打包强制记录的字节对齐”是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

硬盘4K对齐是啥意思?具体该怎么做?

Qt/Android 中的 APK 文件中未对齐是啥意思?

word中制表位是啥意思?

字节数组是啥意思? [关闭]

算法性能的“每字节周期”是啥意思?

gb是啥意思 gb是啥