“Packed Now Forces Byte Alignment of Records” 的意思是什么?

21
Delphi XE2中的最新内容包括以下内容
引用块:

Packed Now Forces Byte Alignment of Records

如果您有使用打包记录类型的遗留代码,并且要链接到外部DLL或C++,则需要从代码中删除"packed"单词。现在,打包关键字会强制执行字节对齐,而过去并不一定如此。该行为更改与Delphi 2009中的C++对齐兼容性更改相关。

我不理解这句话。我困扰于这一点:而以前它并不一定是这样的。据我所知,打包始终导致记录的字节对齐。有人能给出一个打包的记录不是按字节对齐的例子吗?显然,这必须在早期版本中实现。
为什么文档中说“如果您想使用外部DLL或C++链接,您需要从代码中删除packed一词”?如果外部代码使用#pragma pack(1),那么如果不允许使用packed,我们该怎么办? $ALIGN指令呢?{$A1}和{$A-}是否相当于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 位版本上产生相同的输出。

6
请记住,有两种对齐方式:记录内字段的对齐方式(这就是"packed"所影响的)和记录本身的对齐方式,比如在这些记录的数组中。历史上,我相信"packed"并没有影响记录本身的对齐方式,后来这种情况被改变了,或者反过来。 - dthorpe
孜孜不倦的谷歌搜索揭示了你的问题和相关文章。我也不明白,迫不及待地想要一个例子。是在打包记录中拆开数组,还是在变体记录中?目前我的电脑是Linux系统,否则我会很想尝试一下。 - Tony Hopkinson
@dthorpe 嗨Danny。我知道布局和对齐之间的区别。Delphi文档现在记录了紧凑记录的对齐方式为1。但是根据我的经验,这一直都是如此。在D6中肯定是这样的。所以紧凑确实会影响布局和对齐。你是说如果回溯到足够早的版本,比如D1,那么紧凑只会影响布局吗? - David Heffernan
我记得在Kylix中出现了这个问题,需要进行相当多的内部讨论,但我不记得事情的细节是如何解决的。 - dthorpe
4个回答

5
最新更新的文档已经删除了这个问题所基于的所有文本。我的结论是原始文本只是一个文档错误。

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语法。这是最近的吗? - David Heffernan
相关文档在这里:http://docwiki.embarcadero.com/RADStudio/zh-cn/Internal_Data_Formats#Record_Types - David Heffernan
1
我偶然发现了它,但找不到任何关于它的文档。我不确定你的假设是否正确:在线帮助提到“$A指令控制Delphi记录类型和类结构中字段的对齐”,而上面的引用则说“记录的对齐”。 - Uwe Raabe
我们都知道文档是多么的不稳定。通过试错,可以发现packed、$A-和$A1都会导致记录对齐为1。 - David Heffernan
1
如果我们认真对待QC网站,那么这种行为被认为是一个“缺陷”。 - Sertac Akyuz
@Uwe 请看我对问题的更新。专注于对齐,我无法看出版本更改了什么。 - David Heffernan

1
据我所记,自Delphi 5-6编译器版本以来,record一直被打包。
然后,出于性能原因,普通的record字段会根据项目选项中的设置进行对齐。如果在那里定义了一个packed record,则记录内部不会有任何对齐。
你引用的文本似乎与XE2无关,而是与Delphi 2009的更改有关。请参见this Blog entry以了解历史目的。
我猜"'Packed' Now Forces Byte Alignment of Records"指的是Delphi 2009 {$OLDTYPELAYOUT ON}功能 - 在XE2之前可能存在一些实现问题。我同意你的看法:这听起来像是一个文档问题。

我对XE2的新特性的理解是,它涉及记录本身的对齐,而不是记录的布局,这是由$OLDTYPELAYOUT控制的。我最好的猜测是,What's New中指向内部数据格式的链接是为了预期将内容添加到内部数据格式中,但该内容尚未被添加。但我很可能是错的。对我来说仍然是个完全的谜团! - David Heffernan
1
关于 $Align{$A1}{$A-},据我所知,它们在所有版本的 Delphi(包括 XE2)中都相当于 packed - 这是根据我使用这个编译器编译的代码判断的。 - Arnaud Bouchez

-1

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”保留字指示编译器优化大小而非速度,而省略“packed”,编译器通常会优化速度而非大小。 在这种情况下,该结构将被优化,只需占用30个字节。

我再次强调“可能”,因为这仍然取决于编译器。 “packed”关键字只是要求将数据紧密地打包在一起,但并没有实际说明如何打包。 因此,一些编译器忽略了该关键字,而其他编译器则使用“packed”来禁用字对齐。

在后一种情况下,通常会发生的是每个元素都与处理器的字大小对齐。
你需要意识到这里处理器的字大小通常是寄存器大小,而不仅仅是“16位”,因为这已经成为了普遍的定义。例如,在32位处理器的情况下,字大小为32位(尽管我们通常将字定义为16位 - 这只是历史的遗留问题)。
从某种意义上说,它是磁盘扇区的“内存”等效物。
通过打包数据,您可能会有整数值跨越字边界,因此需要更多的内存操作(就像如果记录跨越扇区边界,则需要读取两个磁盘扇区)。
所以基本上这个变化的意思是,Delphi现在完全使用打包关键字,并强制进行字节对齐(而不是字对齐)。
希望这足以解释清楚。实际上,这是一个比我可以在几段话中合理回答的更大的主题。

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

DavidH> 我无法想象你是说编译器能够在相同的输入下生成不同的输出

是的,David,这正是我所说的。 不一定是在同一编译器的下一次编译中,但肯定会在编译器版本、品牌和不同平台之间出现差异。

TonyH > 我认为我们大多数人都认为 packed = 字节对齐,所以现在我们正在为它不适用的场景而苦恼。 @David Heffernan问是否有人知道

Tony,你在这里抓住了关键点。 你假设packed像编译器指令一样工作,指定了数据内存的组织方式。 这不是它的意思。 一个更好的类比是考虑代码生成的优化指令。 你知道代码正在被优化,但你不一定知道底层生成的代码是什么,也不需要知道。

我可以给你很多例子。 假设您有以下内容(我使用数组,但同样适用于记录)

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

如果你打印一个myarray的SizeOf,你会期望得到什么呢? 答案是delphi不能保证以任何特定的方式实现压缩。 因此,答案可能是8个字节(如果你将每个元素对齐到字节)。 同样,delphi将这些打包为位也是有效的,因此整个数组可以适合1个字节。 如果编译器决定不在字节边界上进行优化并且在16位架构上运行,则可能为16个字节。 是的,从速度上讲效率会降低,但打包的整个目的是为了优化大小而非速度。 只要开发人员简单地访问数组元素并且不做任何假设并尝试自己的指针数学来访问底层数据,无论它做什么都对开发人员是不可见的。
同样,你甚至不能保证原始类型的内部组织-它是平台相关的,而不是编译器相关的。例如,如果你在多个架构之间工作,你会意识到围绕一个整数有多少位以及该整数中的字节是否按字节顺序或反向字节顺序存储等问题。 正是这些类型的架构问题,才发展出了各种技术,如逗号分隔文件、xml等,以提供可靠的共同平台。
总结一下,David,问题在于你实际上问错了问题。如果你将Embarcadero的引语放在我所说的背景下考虑,你会发现它与我的观点是一致的。

2
我从未见过任何版本的Delphi编译器将packed array[1..8] of boolean打包为一个byte。FreePascal可以做到这一点 - 这对于像ARM这样只有几KB RAM的嵌入式目标非常有用。但是我从未见过Delphi编译器执行这种位打包,而这在C编译器中非常普遍。 - Arnaud Bouchez
@DavidHeffernan fpc报错:"Error: The address cannot be taken of bit packed array elements and record fields"(除了每个位占用一个完整字节的模式),是的,C数组不能进行打包,但是可以使用C位域:struct { unsigned bit1 : 1; unsigned bit2 : 1; } 在这样的结构体中,您也不能获取位域的地址。 - user743382
1
关于FPC位压缩数组,RTFM - 请参阅http://www.freepascal.org/docs-html/ref/refse14.html#QQ2-39-51和http://www.freepascal.org/docs-html/ref/refsu15.html#x39-460003.3.1 对于FPC @myArray[1]可能会返回与@myArray[0]相同的值,或者根本无法编译(我猜的)- 但您永远不会使用@myArray[1],而是直接使用myArray[1]获取或设置布尔值。Delphi缺乏pack/unpack函数。 - Arnaud Bouchez
@DavidHeffernan 我只是引用FPC的话,因为我不理解Peter所说的Delphi位打包功能。而这个功能在FPC中确实存在,并且在其嵌入式使用中非常普遍(远离Delphi,我承认)。 - Arnaud Bouchez
@DavidHeffernan 手册说明,在 MACPAS 模式下或 $BITPACKING 指令设置为 ON 时,packed 等同于 Bitpacked 关键字。 - Arnaud Bouchez
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接