SizeOf 枚举集,32 位与 64 位和内存对齐
SizeOf set of enums, 32 bit vs 64 bit and memory alignment
给定一个包含 33 个项目(Ceil (33 / 8)
= 5 个字节)的枚举 TEnum
和一个 TEnumSet = Set of TEnum
,SizeOf (TEnumSet)
在 [=33= 时给出不同的结果] 32 位与 64 位 Windows:
- 32 位:根据上面的计算 5 个字节
- 64 位:8 个字节
当增加枚举中的元素数量时,大小将变化为,例如,在 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位编译器没有。
至于如何解决你的问题,那么我可以看到两种主要的方法:
- 在你的64位程序中,逐字段读写记录。对于受此 32 位与 64 位问题影响的字段,您将不得不引入特殊代码来读取和写入字段的前 5 个字节。
- 更改您的记录定义以用
array [0..4] of Byte
替换集,然后引入一个 属性 将集类型映射到该 5 字节数组。
给定一个包含 33 个项目(Ceil (33 / 8)
= 5 个字节)的枚举 TEnum
和一个 TEnumSet = Set of TEnum
,SizeOf (TEnumSet)
在 [=33= 时给出不同的结果] 32 位与 64 位 Windows:
- 32 位:根据上面的计算 5 个字节
- 64 位:8 个字节
当增加枚举中的元素数量时,大小将变化为,例如,在 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位编译器没有。
至于如何解决你的问题,那么我可以看到两种主要的方法:
- 在你的64位程序中,逐字段读写记录。对于受此 32 位与 64 位问题影响的字段,您将不得不引入特殊代码来读取和写入字段的前 5 个字节。
- 更改您的记录定义以用
array [0..4] of Byte
替换集,然后引入一个 属性 将集类型映射到该 5 字节数组。