Delphi 7与2009(和2010)相比,记录大小

8

我在将Delphi 7的代码转换为2010时遇到了一个奇怪的问题。它与记录有关。下面定义的记录,在D7中大小为432字节,在D2009(和2010)中为496字节。我知道,一个简单的解决方案是将其设置为紧凑记录,那么所有版本都变成426字节......然而,我们存储了数据,并将记录流式传输,现在我们正在尝试使用更新的语言读取这些流。

TToTry = Record
 a,b,c,d : Extended;
 e,f,g,h : Extended;
 i : String[15];
 j,k,l,m,n,o,p,q,r,s,t : Array[1..3] of Extended; End;

在调查这个问题时,我创建了另一条记录,但不知何故,大小相同?该记录较小,但具有相同的数据类型,在所有语言版本中大小相同。

TMyRecord = Record
Ext1  : Extended;
Ext2  : Extended;
Ext3  : Extended;
Ext4  : Extended;
Ext5  : Extended;
Ext6  : Extended;
Int1  : Integer;
Int2  : Integer;
char1 : AnsiChar;
char2 : AnsiChar;
MyString  : String[15];
Arr1  : Array[1..3] of Extended;
Arr2  : Array[1..3] of Extended; end;

有人知道为什么一个记录如此不同,而另一个记录相同吗?这肯定与Delphi中的字节边界对齐有关。但是从一个版本到下一个版本发生了什么巨大的变化呢?


我知道 Delphi 记录的默认字节对齐在最近的版本中发生了改变(我想是在2009年),但我不确定具体细节。 - Mason Wheeler
5个回答

14

首先,您将一个非紧凑的记录存储到磁盘中。在产品发布之间,字段和数组的打包允许进行更改,因为通常内存中的布局在进程外不可见。您打破了这个规则。

如果Delphi 7和Delphi 2009之间的字节填充默认值发生了变化,请查找D7中的默认值,并在Delphi 2009中设置为相同的值。

还要检查数组打包默认值。我记不清是否有单独的设置。

在Delphi 2009的调试内存视图中查看记录结构。额外的大小可能全部或部分归因于记录本身的填充(而不是其中的字段),以便在数组中使用记录时,数组元素位于快速机器边界上。

如果这些都没有帮助,请在D2009中创建一个临时紧凑的记录类型,并在实际数据字段之间手动插入字节填充字段,直到记录大小和字段对齐与D7布局匹配为止。这不仅涉及大小,还涉及字段对齐。使用此临时紧凑的记录类型读取旧数据文件。然后逐个字段将数据传输到D2009中的“真实”记录类型中,并编写新文件。

在此过程中,确保在D2009中对该记录类型进行紧凑打包,以防止此类情况再次发生。


4
假设您没有返回到D7并修改那里的代码来打包记录结构并编写打包记录的文件,则需要手动匹配字节填充比事后少一些工作。请注意,这不会改变原始意思。 - dthorpe
8
回顾应该也是一种教育。将拆包记录写入文件应该只发生一次在整个职业生涯中。 ;> - dthorpe
1
这个回答并没有真正回答问题。问题是为什么在两个 Delphi 版本之间更改的记录布局规则会影响第一个记录而不影响第二个记录。这个回答提供了解决由这些更改导致的问题的建议,但这不是问题所问的。是关于 TToTry 什么使其容易受到不同对齐规则的影响,而 TMyRecord 看起来免疫? - Rob Kennedy
7
我不能回答关于产品开发团队决定进行这种或那种更改的政策问题。 至于为什么TMyRecord的大小在版本发布之间保持稳定,是否有人检查项目文件以查看是否显式设置了对齐方式?TMyRecord也是与TToTry非常不同的结构。 TToTry由8个10字节扩展字段和12个3个10字节扩展字段的数组主导。TMyRecord包含整数和字节,以及更少的10字节扩展字段。在TMyRecord中需要进行N字对齐的内容要少得多。 - dthorpe
1
“相对较新”的评论是开玩笑吗?我知道他是谁,@Ken。发帖的问题没有问为什么做了更改,我的追问也没有。这个问题确实问到为什么更改会影响一个记录而不影响另一个记录,我认为丹尼在评论中对这个问题的回答相当模糊。虽然这是实用的信息,但问题并没有问如何修复这些差异。 - Rob Kennedy
显示剩余4条评论

7

我相信你遇到了一个“特性”!使用D2007和TToTry,我无法得到一个合理的大小,因此我不得不通过调试器查找字段地址。

首先,下面记录的大小:

{$A8}
type
  TToTry = record
    j: array[1..3] of Extended;
    k: array[1..3] of Extended;
    l: array[1..3] of Extended;
    m: array[1..3] of Extended;
  end;

128(32*4)是正确的结果,因为 Extended 占用 10 字节,30 字节需要对齐到 32 字节。

但是这个记录的大小,

{$A8}
type
  TToTry = record
    j, k, l, m: array[1..3] of Extended;
  end;

这里的120 (30*4)是计算出来的。这个结果有些出乎意料,因为字段应该仍然在8字节边界上对齐。

(我没有D7来验证,但我的想法是:)
现在我们知道分组字段是紧凑排列的,因此在D7上的对齐方式是8字节,并且您的记录几乎是紧凑排列的;

TToTry = Record 
 a,b,c,d : Extended;    // 40 bytes (8*5)
 e,f,g,h : Extended;    // 40 bytes (8*5)
 i : String[15];        // 16 bytes (8*2)
 j,k,l,m,n,o,p,q,r,s,t: Array[1..3] of Extended; // 330 bytes
End; 

编译器会在最后一组填充6个字节,使其成为8的倍数,然后你会得到40+40+16+336 = 432字节。使用D2009/D2010,你要么声明每个字段 - 不进行分组,要么改变行为。无论哪种方式,都要压缩记录并在末尾添加一个6字节的虚拟字段,这样就可以了。如果这样还不行,用D7查看记录的字段地址,然后使用压缩记录创建一个完全相同的D2009版本,并根据需要使用虚拟字段,导入存储的数据后可以删除虚拟字段。 --我从未见过这样的行为,并且在任何地方都找不到它的文档。尽管它很像一个特性,但我不确定它是否是一个错误。我不知道D2009或D2010的行为是否相同,请测试一下。如果是的话,为了获得预期的结果 - 不要有半压缩记录 - 不要懒惰,为非压缩记录声明每个字段。

Delphi 2009 中第二条记录的大小为128字节。 - kludg
@Serg - 感谢您查找,所以这不是一个功能。事实上,在使用D2007进行测试时,我遇到了其他奇怪/意外的对齐方式。我很高兴后来的版本已经解决了这个问题! - Sertac Akyuz

3
我知道这是一篇旧文章,但我昨天在Delphi XE2中遇到了同样的问题。我有几个类型文件是使用Turbo Delphi 2006应用程序编写的,并且我也犯了不使用Packed records的错误。我的情况更加复杂,因为我正在使用可变长度记录(在z/OS世界中我们称之为这个,不确定Delphi术语是否相同),因此错误对齐导致记录关键标记不能出现在正确的字段中,从而导致所有子记录都不在正确的位置,使整个文件无用。
我发现你可以将对齐编译指令放在一个特定的代码块周围。除此之外,我还发现了这个小宝石:{$OLDTYPELAYOUT ON}
{$OLDTYPELAYOUT ON}
 RMasterRecord = Record       
    mKey       : word;
    mDeleted   : boolean;
    case mType : ANSIChar of
       'V' : //Info
          (Vers : RVersionLayout);
       […]
{$OLDTYPELAYOUT OFF}

我只在写入和读取文件的主记录周围放置了指令,而没有在子记录定义周围放置。这解决了我的问题,现在我可以在 XE2 中编译并读取我的 TD2006 文件。下次我会使用压缩记录(或更好的 SQLite)。但我想分享一下这个,因为这个网站多年来帮助了我很多。
您可以在此处了解有关 $OLDTYPELAYOUT 的更多信息:http://docwiki.embarcadero.com/RADStudio/XE5/en/Internal_Data_Formats#Record_Types。顺便说一句,第一个修复它的是 {$A4} 但当我发现 {$OLDTYPELAYOUT ON}时,我切换到使用它,因为它更明显。

2
应用{$A8}指令并不意味着所有记录字段都按8字节边界对齐 - 编译器使用不同的对齐策略。例如,

的大小
{$A8}
type
  TToTry = record
    a: byte;
    b: word;
    c: longword;
  end;

在Delphi 2009中,8个字节是因为编译器将2字节值对齐到2字节边界,4字节值对齐到4字节边界,上面示例中唯一的实际对齐是b字段对齐到2字节边界。

至于原始问题,在Delphi 7和Delphi 2009之间有什么变化 - 请阅读Sertac Akyuz的答案和我的评论。


啊,好的!这就是我在回复你对我的答案的评论时所说的“奇怪/意外”的意思。但这是否是可以预料的呢?文档中说:“在{$A8}或{$A+}状态下,未声明为紧凑型修饰符的记录类型中的字段和类结构中的字段都会对齐到四个字的边界上。” - Sertac Akyuz
2
@Sertac:四字节对齐仅适用于长度至少为8个字节的数据类型。在x86架构上,将一个字节字段放在8字节边界上几乎没有什么好处。(这可能有助于L2缓存中的高速缓存行冲突,但这真的非常微小)。正如Serg所说,数据类型按其自然边界(=数据大小)对齐到$A/n/大小。请注意,有多个对齐点需要考虑:从结构体开始的字段偏移量,数组内部数据的对齐以及记录本身的填充,以便在数组中使用时能够对齐良好。 - dthorpe
1
@dthorpe - 谢谢,一切都很清楚。但我希望文档中的“结构类型”或“对齐字段”主题在这方面更加明确,特别是对于像我这样没有底层架构知识的人来说。 - Sertac Akyuz

2
我认为默认的对齐方式变得更宽了。在后续版本中指定对齐方式为4,看看是否能达到你想要的效果。
将要写入磁盘的任何记录都应该存储为紧凑格式,以免出现问题。
编辑:既然没有一个对齐方式起作用(这让我很惊讶),那么我会回到原始数据,并弄清楚它的真正对齐方式。用类似 $FF 的内容填充记录,放入数据并写出来——看看哪些 $FF 能够留存下来。接下来,把新记录打包,并添加填充以匹配旧记录中的填充。
另外一件事:这实际上只是一个记录吗?在旧时代,我曾经使用对象作为假记录,并进行继承——糟糕的是,在继承点上应用了正常的对齐方式,我无法阻止它。最终,我不得不在数据之前填充,以使强制对齐不破坏我的数据。(这是要去API,必须正确,我不能单独处理字段。)

如何“指定对齐方式”? - Jason Nethercott
找到了:{$A4}。但是没有帮助。现在的数字是464。我尝试了所有其他可能性{$A?},但都不匹配。 - Jason Nethercott

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