7

I found that a SizeOf(set) i different in 32-bit and 64-bit, the example below shows 5 byte for 32-bit and 8 for 64-bit. But i found nothing information about changes in SizeOf(sets) for 64-bit. Is there any Embarcadero documentation about it or compiler directive to get a similar results on 32 and 64-bit.

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses System.SysUtils;

type
{ Enumeration of properties}
TProperty1 = (p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14,
  p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28,
  p29, p30, p31, p32, p33, p34, p35, p36, p37);

TProperties1 = set of TProperty1;

begin
WriteLn(SizeOf(TProperties1));
ReadLn;
end.
Denis Sletkov
  • 221
  • 1
  • 4
  • 1
    Why does this matter to you? It seems that the 64 bit compiler is using a 64 bit integer for the set, but the 32 bit compiler does something else altogether. Lord knows what. Would be interesting to see what the compiler's do with sets of smaller base types. I'm sure there's no docs and no compiler switches. You'll have to live with this. – David Heffernan May 19 '15 at 21:50
  • 1
    I save a set into stream in a following way Stream.Write(set, SizeOf(set)). So the 32-bit and 64-bit app create different streams. I checked a smaller sets (up to 32 elements) and their sizes is equal. Heh, i checked a set with 66 elements and sizeof is 9 on 32 and 64-bit. – Denis Sletkov May 19 '15 at 21:59
  • 1
    Very related question (almost a duplicate): [How to save/load set of types](http://stackoverflow.com/questions/9553510/how-to-save-load-set-of-types). The answers that show various ways to convert a set a a bunch of bits. – GolezTrol May 19 '15 at 22:18
  • To save as binary you should save just enough bytes and no more, I guess no point saving 4 bytes when 3 suffice. So I'd base the persistence on the size of the base enum. And hope that you never change the base enum!!! – David Heffernan May 20 '15 at 07:07
  • 2
    FWIW; documentation (http://docwiki.embarcadero.com/RADStudio/XE8/en/Internal_Data_Formats#Set_Types) says: "When possible, the compiler stores sets in CPU registers". In 64 bit, registers can be, well, up to 64 bit. – Rudy Velthuis May 20 '15 at 07:43

2 Answers2

6

To answer your question. I couldn't find anything on the Embarcadero site regarding the differences or a compiler directive to change the behavior. My research indicates the following:

Sets have the following sizes in bytes in 32 bit:

  • Up to 8 elements - 1 Byte
  • 9 to 16 elements - 2 Bytes
  • 17 to 32 elements - 4 Bytes

From this point onwards it adds adds bytes as needed, one at a time. So 33 to 40 elements uses 5 bytes and 41 to 48 elements uses 6 bytes.

In 64 bit mode, things are slightly different:

  • Up to 8 elements - 1 Byte
  • 9 to 16 elements - 2 Bytes
  • 17 to 32 elements - 4 Bytes
  • 33 to 64 elements - 8 Bytes

From this point onwards it adds adds bytes as needed, one at a time. So 65 to 72 elements uses 9 bytes and 73 to 80 elements uses 10 bytes.

To get around this you are going to need to either use something like WriteSet in TWriter.WriteProperty and TReader.ReadSet or you can do something like this:

procedure SaveSetToStream(aStream: TStream; const aSet: TProperties1);
var
  streamData: array[0..7] of byte;
begin
  Assert(SizeOf(aSet) <= SizeOf(streamData), 'Set is too large to save. Increase the array length.');
  FillChar(streamData, SizeOf(streamData), 0);
  Move(aSet, streamData, SizeOf(aSet));
  aStream.Write(streamData, SizeOf(streamData));
end;

function ReadFromStream(aStream: TStream): TProperties1;
var
  streamData: array[0..7] of byte;
begin
  Assert(SizeOf(Result) <= SizeOf(streamData), 'Set is too large to load. Increase the array length.');
  aStream.Read(streamData, SizeOf(streamData));
  Move(streamData, Result, SizeOf(Result));
end;
Graymatter
  • 6,529
  • 2
  • 30
  • 50
  • 2
    So this seems reasonable. The compiler uses types that are supported by hardware if possible, and then when it runs out of them, uses non native types. – David Heffernan May 20 '15 at 06:39
  • Seems logical to me: since `Set` works bitwise it needs 8 bits for 8 elements, thus 1 byte. One more element will require 1 more byte, until 16 bits/elements are full. And so on. And surely as a compromise of speed/memory odd sizes (3, 5, 6 or 7 bytes) aren't used while within the platform. – AmigoJack Aug 30 '21 at 13:39
0

Another workaround, to make sure a 32 bit machine can read a stream from a 64 bit machine and vice-versa is to create a function

function SizeCheck( const p : integer ) : integer;
begin
  if p in [5..8 ] then Result := 8 else Result := p; // adjust for 64 bit set sizes
end;  

and then use

Stream.Write(set, SizeCheck(SizeOf(set)));

Obviously only use for sets.

Dsm
  • 5,870
  • 20
  • 24
  • There is no problem to create workaround. But i do not understand this is a compiler bug or a compiler feature. – Denis Sletkov May 20 '15 at 18:32
  • @dsm You need to be careful with reading from the stream using this option because you will overwrite the variables that follow the set. – Graymatter May 20 '15 at 18:41