program BytesFiddle; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TThing = class private FBuf : TBytes; FData : TBytes; function GetThingData: TBytes; function GetThingType: Byte; public property ThingType : Byte read GetThingType; property ThingData : TBytes read GetThingData; constructor CreateThing(const AThingType : Byte; const AThingData: TBytes); end; { TThing1 } constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes); begin SetLength(FBuf,Length(AThingData) + 1); FBuf[0] := AThingType; Move(AThingData[0],FBuf[1],Length(AThingData)); FData := @FBuf[1]; SetLength(FData,Length(FBuf) - 1); end; function TThing.GetThingData: TBytes; begin Result := FData; end; function TThing.GetThingType: Byte; begin Result := FBuf[0]; end; var Thing1,Thing2 : TThing; begin try Thing1 := TThing.CreateThing(0,TEncoding.UTF8.GetBytes('Sneetch')); Thing2 := TThing.CreateThing(1,TEncoding.UTF8.GetBytes('Star Belly Sneetch')); Writeln(TEncoding.UTF8.GetString(Thing2.ThingData)); Writeln(Format('Type %d',[Thing2.ThingType])); Writeln(TEncoding.UTF8.GetString(Thing1.ThingData)); Writeln(Format('Type %d',[Thing1.ThingType])); ReadLn; except on E: Exception do Writeln(E.ClassName,': ',E.Message); end; end.
解决方法
如果您使用调试器浏览代码,您可以看到会发生什么。
在Thing1初始化之后,您可以看到FData已经填满了所有的零。
奇怪的是Thing2很好。
因此错误在CreateThing中。让我们进一步调查…
在奇怪命名的构造函数CreateThing中,你有以下一行:
FData := @FBuf[1];
这看起来像一个简单的赋值,但是真的是调用DynArrayAssign
Project97.dpr.32: FData := @FBuf[1]; 0042373A 8B45FC mov eax,[ebp-$04] 0042373D 83C008 add eax,$08 00423743 8B5204 mov edx,[edx+$04] 00423746 42 inc edx 00423747 8B0DE03C4000 mov ecx,[$00403ce0] 0042374D E8E66DFEFF call @DynArrayAsg <<-- lots of stuff happening here.
DynArrayAsg执行的检查之一是检查源动态数组是否为空。
DynArrayAsg也做了一些你需要注意的事情。
我们先看看the structure of a dynamic array;它不仅仅是一个数组的简单指针!
Offset 32/64 | Contents --------------+-------------------------------------------------------------- -8/-12 | 32 bit reference count -4/-8 | 32 or 64 bit length indicator 0/ 0 | data of the array.
执行FData = @FBuf [1]你正在搞乱动态数组的前缀字段。
@Fbuf [1]前面的4个字节被解释为长度。
对于Thing1,这些是:
-8 (refcnt) -4 (len) 0 (data) FBuf: 01 00 00 00 08 00 00 00 00 'S' 'n' ..... FData: 00 00 00 08 00 00 00 00 .............. //Hey that's a zero length.
糟糕的是,当DynArrayAsg开始调查时,它看到它认为的是源的分配长度为零,即认为源是空的,不分配任何东西。它使FData保持不变!
Thing2是否按预期工作?
它看起来像它,但它实际上失败了一个坏的方式,让我告诉你。
你已经成功地将运行时欺骗了@FBuf [1]是对动态数组的有效引用。
因为这样,FData指针已被更新为指向FBuf [1](到目前为止这么好),并且FData的引用计数已经增加1(不好),运行时也增加了保存动态的内存块数组,它认为是FData的正确大小(不好)。
-8 (refcnt) -4 (len) 0 (data) FBuf: 01 01 00 00 13 00 00 00 01 'S' 'n' ..... FData: 01 00 00 13 00 00 00 01 'S' ..............
Oops FData现在有318,767,105的计数,长度为16,777,216字节。
FBuf的长度也有所增加,但其计数现在为257。
这就是为什么你需要调用SetLength来消除大量的内存分配。这仍然不能解决引用计数。
过度分配可能导致内存不足错误(特别是64位),而古怪的引用计数会导致内存泄漏,因为您的数组永远不会被释放。
解决方案
根据David的答案:启用键入的检查指针:{$ TYPEDADDRESS ON}
您可以通过将FData定义为普通PAnsiChar或PByte来修复代码。
如果您确保始终终止您的分配到FBuf与双零FData将按预期工作。
使FData像这样:
TBuffer = record private FData : PByte; function GetLength: cardinal; function GetType: byte; public class operator implicit(const A: TBytes): TBuffer; class operator implicit(const A: TBuffer): PByte; property Length: cardinal read GetLength; property DataType: byte read GetType; end;
重写CreateThing,如下所示:
constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes); begin SetLength(FBuf,Length(AThingData) + Sizeof(AThingType) + 2); FBuf[0] := AThingType; Move(AThingData[0],Length(AThingData)); FBuf[Lengh(FBuf)-1]:= 0; FBuf[Lengh(FBuf)-2]:= 0; //trailing zeros for compatibility with pansichar FData := FBuf; //will call the implicit class operator. end; class operator TBuffer.implicit(const A: TBytes): TBuffer; begin Result.FData:= PByte(@A[1]); end;
我不明白为什么试图超越编译器。
为什么不这样声明FData呢?
type TMyData = record DataType: byte; Buffer: Ansistring; ....
并与之配合。