【问题标题】:Delphi 7 compared to 2009 (& 2010) Record sizesDelphi 7 与 2009 (& 2010) 相比记录大小
【发布时间】:2010-07-08 17:43:23
【问题描述】:

在将代码从 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 年)中发生了变化,但我不确定细节。

标签: delphi delphi-2009 delphi-2010 record


【解决方案1】:

嗯,第一个问题是您将非压缩记录存储到磁盘。字段和数组打包允许在产品发布之间更改,因为通常内存中的布局在流程之外是不可见的。你违反了这条规则。

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

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

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

如果这些都没有帮助,请在 D2009 中创建一个临时打包记录类型,并在实际数据字段之间手动插入字节填充字段,直到记录大小和字段对齐方式与 D7 布局匹配。这不仅仅是大小,它是字段对齐。使用此临时打包记录读取您的旧数据文件。然后在 D2009 中将数据逐个字段转换为您的“真实”记录类型并写出一个新文件。

当你这样做的时候,把那个记录类型打包到 D2009 中,这样就不会再发生这种情况了。

【讨论】:

  • 这一切都假设您没有选择返回 D7 并修改那里的代码以打包记录结构并写出打包记录的文件。这比事后手动尝试匹配字节填充要少。
  • 后见之明也应该具有教育意义。每个职业应该只将解压的记录写入文件一次。 ;>
  • 这并不能真正回答问题。该问题询问为什么在两个 Delphi 版本之间更改的记录布局规则会影响第一个记录,但不会影响第二个记录。这个答案提供了有关如何解决这些更改导致的问题的建议,但这不是问题所要求的。 TToTry 是什么使它容易受到不同对齐规则的影响,而 TMyRecord 似乎免疫?
  • 我无法回答有关产品开发团队为何决定做出这样或那样的改变的政策问题。至于为什么 TMyRecord 大小在版本之间是稳定的,有没有人检查过项目文件以查看是否明确设置了对齐? TMyRecord 也是与 TToTry 非常不同的结构。 TToTry 由 8 个 10 字节扩展字段和 12 个 3 个 10 字节扩展字段的数组支配。 TMyRecord 抛出整数和字节以及少得多的 10 字节扩展。 TMyRecord 中可能需要填充以进行 N 字对齐的内容要少得多。
  • 那个“相对较新”的评论是个玩笑,对吧?我知道他是谁,@Ken。发布的问题没有询问为什么要进行更改,我的后续评论也没有。问题确实询问为什么更改会影响一个记录而不影响另一个记录,我认为 Danny 对这个问题的回答(在评论中)相当模糊。虽然它是实用的信息,但问题并没有问如何解决这些差异。
【解决方案2】:

我相信您已经完成了一个功能!。我无法通过您的 TToTry 和 D2007 获得合理的大小,因此我不得不使用调试器查找字段地址;

首先,以下记录的大小,

{$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字节
  • @Serg - 感谢您查找它,所以它不是一个功能。事实上,我在使用 D2007 进行测试时遇到了其他奇怪/意外的对齐方式。我很高兴以后的版本能直截了当!
【解决方案3】:

我知道这是一篇旧帖子,但我昨天在 Delphi XE2 上遇到了同样的问题。我有几个使用 Turbo Delphi 2006 应用程序写出的类型文件,我也犯了不使用打包记录的错误。我的情况很复杂,因为我使用的是可变长度记录(这就是我们在 z/OS 世界中所说的,不是 100% 确定 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} 时,我将它切换为使用它,因为它更明显。

【讨论】:

    【解决方案4】:

    我相信默认的对齐方式更宽了。在后面的版本中指定对齐 4,看看它是否按照你想要的方式出现。

    对于将来,您应该确保将要写入磁盘的任何记录都打包存储,这样您就不会被这样烧毁。

    编辑:由于没有对齐工作(这让我感到惊讶),我会回到原来的并弄清楚它是如何对齐的。用 $FF 之类的东西填充记录,将数据放入并写出——看看 $FF 在哪里幸存下来。获取新记录,将其打包并添加填充符以匹配旧记录中的填充。

    一件事:这实际上只是一张唱片吗?在过去,我使用对象作为带有继承的假记录——哎呀,在继承时应用了正常对齐,我无法阻止它。我最终不得不在数据之前填充,以使强制对齐不会破坏我的数据。 (这是一个 API,它不得不是对的,我无法独立处理这些字段。)

    【讨论】:

    • 如何“指定对齐方式”?
    • 找到它:{$A4}。但这没有帮助。这个数字现在是 464。我尝试了所有其他 {$A?} 的可能性,但没有一个匹配。
    【解决方案5】:

    应用 {$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:四字对齐仅适用于长度至少为 8 个字节的数据类型。通过将字节字段放在 8 字节边界上,x86 架构几乎没有什么好处。 (它可以帮助解决 L2 缓存中的缓存行冲突,但这真的很小)。正如 Serg 所说,数据类型与其自然边界(= 数据大小)对齐到 $A/n/ 大小。请注意,需要考虑多个对齐点:字段与结构开头的偏移量、数组内数据的对齐方式以及记录本身的填充以在数组中使用时对齐良好。
    • @dthorpe - 谢谢,一切都说得通。但我希望文档中的“结构化类型”或“对齐字段”主题对此更加明确,尤其是对于像我这样不了解底层架构的人..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    • 2011-05-28
    • 1970-01-01
    • 2011-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多