delphi – 为什么两个TBytes不能使用重叠的数据?

前端之家收集整理的这篇文章主要介绍了delphi – 为什么两个TBytes不能使用重叠的数据?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
考虑以下XE6代码。目的是ThingData应该写入Thing1& Th2,但不是。这是为什么?
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;  
    ....

并与之配合。

猜你在找的Delphi相关文章