SizeOf 枚举集,32 位与 64 位和内存对齐

SizeOf set of enums, 32 bit vs 64 bit and memory alignment

给定一个包含 33 个项目(Ceil (33 / 8) = 5 个字节)的枚举 TEnum 和一个 TEnumSet = Set of TEnumSizeOf (TEnumSet) 在 [=33= 时给出不同的结果] 32 位与 64 位 Windows:

当增加枚举中的元素数量时,大小将变化为,例如,在 32 位中为 6 个字节,而在 64 位中,它仍为 8 个字节。好像 64 位中的内存对齐将大小四舍五入到最接近的 XX 倍数? (不是 8,较小的枚举确实会产生 2 或 4 的集合大小)。而 2 的幂也很可能不是这种情况?

在任何情况下:当从 32 位程序将文件读​​取到作为缓冲区写入的打包记录时,这会导致问题。尝试将同一个文件读回 64 位程序,因为打包的记录大小不匹配(记录包含这个不匹配的集合等),读取失败。

我试着在编译器选项中寻找与内存对齐相关的一些选项:有一个记录内存对齐的选项,但它不影响集合,并且在两种配置中已经相同。

关于为什么该集在 64 位中占用更多内存的任何解释,以及能够在 64 位平台上将文件读入我的打包记录的任何潜在解决方案?

请注意,我无法控制文件的写入:它是使用我无权访问的 32 位程序编写的(因此无法更改写入)。

使用集合的内存大小迟早会导致处理错误。这在处理子类型时变得尤为明显。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TBoolSet=set of boolean;
  TByteSet=set of byte;
  TSubEnum1=5..10;
  TSubSet1=set of TSubEnum1;
  TSubEnum2=205..210;
  TSubSet2=set of TSubEnum2;
var
  i, j: integer;
  a, a1: TByteSet;
  b, b1: TSubSet1;
begin
  try
    writeln('SizeOf(TBoolSet): ', SizeOf(TBoolSet)); //1
    writeln('SizeOf(TByteSet): ', SizeOf(TByteSet)); //32
    writeln('SizeOf(TSubSet1): ', SizeOf(TSubSet1)); //2
    writeln('SizeOf(TSubSet2): ', SizeOf(TSubSet2)); //2
  
   //Assignments are allowed. 
   a := [6, 9];
   b := [6, 9];
   writeln('a = b ?: ', BoolToStr(a = b, true)); //true

   a1 := a + b; //OK
   b1 := a + b; //OL
   a  := [7, 200];
   b1 := a + b; //??? no exception, Value 200 was lost. !
   i  := 0;
   for j in b1 do
     i := succ(i);
   writeln('b1 Count: ', i);

  readln(i);
 except
    on E: Exception do
     Writeln(E.ClassName, ': ', E.Message);
 end;
end.

这是我的测试程序:

{$APPTYPE CONSOLE}

type
  TEnumSet16 = set of 0..16-1;
  TEnumSet17 = set of 0..17-1;
  TEnumSet24 = set of 0..24-1;
  TEnumSet25 = set of 0..25-1;
  TEnumSet32 = set of 0..32-1;
  TEnumSet33 = set of 0..33-1;
  TEnumSet64 = set of 0..64-1;
  TEnumSet65 = set of 0..65-1;

begin
  Writeln(16, ':', SizeOf(TEnumSet16));
  Writeln(17, ':', SizeOf(TEnumSet17));
  Writeln(24, ':', SizeOf(TEnumSet24));
  Writeln(25, ':', SizeOf(TEnumSet25));
  Writeln(32, ':', SizeOf(TEnumSet32));
  Writeln(33, ':', SizeOf(TEnumSet33));
  Writeln(64, ':', SizeOf(TEnumSet64));
  Writeln(65, ':', SizeOf(TEnumSet65));
end.

以及输出(我使用的是 XE7,但我希望它在所有版本中都相同):

32 bit 64 bit
16:2 16:2
17:4 17:4
24:4 24:4
25:4 25:4
32:4 32:4
33:5 33:8
64:8 64:8
65:9 65:9

撇开 32 位和 64 位的区别不谈,注意 17 位和 24 位的情况理论上可以适合 3 字节类型,它们存储在 4 字节类型中。

为什么编译器选择使用 4 字节类型而不是 3 字节类型?这只能是允许更高效的代码。对可以直接映射到 CPU 寄存器的数据进行操作比逐字节提取数据更有效,或者在这种情况下,在一个操作中访问两个字节,然后在另一个操作中访问第​​三个字节。

这就指出了为什么在 64 位编译器下 33 到 64 位之间的任何内容都映射到 8 字节类型。 64位编译器有64位寄存器,32位编译器没有。

至于如何解决你的问题,那么我可以看到两种主要的方法:

  1. 在你的64位程序中,逐字段读写记录。对于受此 32 位与 64 位问题影响的字段,您将不得不引入特殊代码来读取和写入字段的前 5 个字节。
  2. 更改您的记录定义以用 array [0..4] of Byte 替换集,然后引入一个 属性 将集类型映射到该 5 字节数组。