gpt4 book ai didi

delphi - 为什么两个 TByte 不能使用重叠数据?

转载 作者:行者123 更新时间:2023-12-03 14:35:21 25 4
gpt4 key购买 nike

考虑以下 XE6 代码。其目的是应将 ThingData 写入 Thing1Thing2 的控制台,但事实并非如此。这是为什么?

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.

最佳答案

让我向您介绍一下这段代码失败的原因以及编译器如何让您搬起石头砸自己的脚。

如果您使用调试器单步执行代码,您可以看到会发生什么。

enter image description here

初始化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 是否按预期工作?
看起来确实如此,但实际上它以一种相当糟糕的方式失败了,让我告诉你。

enter image description here

您已成功欺骗运行时,使其相信 @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' ..............

糟糕 FData 现在的引用计数为 318,767,105,长度为 16,777,216 字节。
FBuf 的长度也增加了,但其引用计数现在为 257。

这就是为什么您需要调用 SetLength 来撤消大量内存过度分配。但这仍然无法修复引用计数。
过度分配可能会导致内存不足错误(尤其是在 64 位上),而古怪的引用计数会导致内存泄漏,因为您的数组永远不会被释放。

解决方案
根据 David 的回答:启用类型化检查指针: {$TYPEDADDRESS ON}

您可以通过将 FData 定义为普通的 PAnsiCharPByte 来修复代码。
如果您确保始终使用双零终止对 FBuf 的分配,FData 将按预期工作。

使FData成为TBuffer,如下所示:

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], FBuf[1], 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 - 为什么两个 TByte 不能使用重叠数据?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39249157/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com