`bitpacked`记录小端机问题

`bitpacked` records on the little-endian machine issue

我正在尝试在 小端计算机 上使用 FreePascal 来读取和解释来自集成电路的数据。数据基本上由紧密位压缩(大部分)big-endian 整数组成,其中一些(实际上很多)未与字节边界对齐 .所以,我尝试使用 FPC 的 bitpacked 记录来解决这个问题,但发现自己陷入了深深的麻烦中。

我尝试读取的第一个结构具有以下格式:

{$BITPACKING ON}  
type
  THeader = bitpacked record
    Magic: Byte;        // format id, 8 bits
    _Type: [=10=]0..$FFF;  // type specifier, 12 bits
    Version: Word;      // data revision, 16 bits
    Flags: [=10=]..$F       // attributes, 4 bits
  end;

这是阅读代码:

procedure TForm1.FormCreate(Sender: TObject);
var
  F: File;
  Header: THeader;
begin
  Writeln(SizeOf(Header), #9, BitSizeOf(Header));   // reports correctly

  Writeln('SizeOf(Header._Type) = ', SizeOf(Header._Type));       // correctly reports 2 bytes
  Writeln('BitSizeOf(Header._Type) = ', BitSizeOf(Header._Type)); // correctly reports 12 bits

  AssignFile(F, 'D:fd8.dat');
  FileMode := fmOpenRead;
  Reset(F, SizeOf(Byte));
  BlockRead(F, Header, SizeOf(Header));

  { data is incorrect beyond this point already }

  //Header._Type := BEtoN(Header._Type);

  Writeln(IntToHex(Header.Magic, SizeOf(Header.Magic) * 2));
  Writeln(IntToHex(BEtoN(Header._Type), SizeOf(Header._Type) * 2));
  Writeln(BEtoN(Header.Version));
end;

但是代码打印的数据完全错误。

这是手动完成的数据和解释:

0000000000: F1 55 BE 3F 0A ...
Magic = F1
_Type = 55B
Version = E3F0
Flags = A

但 FPC 以完全不同且不正确的方式查看数据。由于主机的小字节序,看起来属于字段的半字节(和位)不连续(例如:半字节 B 通常应该属于 _Type 字段和半字节 E - 到Version)。这是来自 Lazarus 的手表 window:

请指教我应该如何处理这种行为。这个non-contiguous bitfield是不是FPC的bug?任何可能的解决方法?

字节数

F1 55 BE 3F 0A

具有以下连续半字节(低半字节在高半字节之前):

1 F 5 5 E B F 3 A 0

如果将它们分别分组为 2、3、4 和 1 个半字节,您将得到:

 1 F     --> $F1    
 5 5 E   --> $E55   // highest nibble last, so E is highest.
 B F 3 A --> $A3FB  // same again: A is highest nibble
 0       --> [=12=]

这对应于您在 Watch window 中看到的结果,而不是您手动解码的结果。

现在,如果数据是大端数据,则您必须使用移位和掩码手动解码:

X.Magic := bytes[0];
X._Type := (bytes[1] shl 4) or (bytes[2] shr 4);
X.Version := ((bytes[2] and [=13=]F) shl 12) or 
             (bytes[3] shl 4) or 
             (bytes[4] shr 4);
X.Flags := bytes[4] and [=13=]F;

我用这个函数从IEEE格式转换成FPC格式:

Type MyReal = Array [1..4] of Byte;

function IeeeToSingle (src:MyReal):Single;
      var x:MyReal; 
          s:single absolute x;
          man:word; 
          exp:word;
          ca:single; 
          cb:cardinal absolute ca; // 4 byte unsigned long int
      begin
        x:=src;
        ca:=s;
        if cb>0 then begin                // not zero
            man := cb shr 16;
            exp := (man and $ff00) - 00;
            if ((exp and  00) <> (man and 00)) then
              MsToIeee := -1; // exponent overflow
            man := (man and  f) or ((man shl 8) and  00);// move sign
            man := man or (exp  shr  1);
            cb := (cb and  $ffff) or (Cardinal(man)  shl 16);
          end;
          IeeeToSingle := ca;
      end;