DynArraySize() 只能正确工作最多 649 个整数元素的数组
DynArraySize() works correctly only up to arrays of 649 integer elements
我在 Delphi 10.2 更新 2 中遇到了一个与 RTTI 相关的问题,并且能够追踪到它的代码量更少(见下文)。
我有一些 TPersistent
-后代 class TMyObj
发布了 属性 类型 TArray<Integer>
。当我通过 GetDynArrayProp()
收到它的值并通过 DynArraySize()
查询它的大小时,这只适用于恰好 649 个元素的大小。在这个特殊计数之上,返回了一些非常大的尺寸值。
请注意,我的数组是从 TDictionary<Integer,Boolean>
的 Keys
属性 的一个实例生成的,它具有自己的 ToArray
方法。
我还尝试修改 TMyObj.GetDynArray
,使其 returns 直接成为 TArray<Integer>
的一个实例,并且它可以正常工作。
因此,我认为这可能以某种神秘的方式相关联。
我使用 DynArraySize()
有什么问题?动态数组的这种神秘行为背后是什么?
program RTTIPropDynArray;
{$APPTYPE CONSOLE}
uses
System.Classes, System.Generics.Collections, System.SysUtils, System.TypInfo;
type
TMyDict = TDictionary<Integer,Boolean>;
TMyArray = TArray<Integer>;
TMyObj = class(TPersistent)
private
FValues: TMyDict;
function GetDynArray: TMyArray;
public
constructor Create(const ACount: Integer);
destructor Destroy; override;
published
property DynArray: TMyArray read GetDynArray;
end;
{ TMyObj }
constructor TMyObj.Create(const ACount: Integer);
begin
FValues := TMyDict.Create;
while FValues.Count < ACount do
FValues.AddOrSetValue(Random(MaxInt), False);
end;
destructor TMyObj.Destroy;
begin
FreeAndNil(FValues);
inherited;
end;
function TMyObj.GetDynArray: TMyArray;
begin
Result := FValues.Keys.ToArray;
end;
function Test(const ACount: Integer): Boolean;
var
LInstance: TMyObj;
LExpectedSize: Integer;
LDynArraySize: Integer;
begin
LInstance := TMyObj.Create(ACount);
try
LExpectedSize := Length(LInstance.DynArray);
LDynArraySize := DynArraySize(GetDynArrayProp(LInstance, 'DynArray'));
Result := LExpectedSize = LDynArraySize;
if not Result then
WriteLn(Format('Expected size: %d; DynArraySize: %d', [LExpectedSize, LDynArraySize]));
finally
LInstance.Free;
end;
end;
var
LCount: Integer;
begin
Randomize;
LCount := 1;
while Test(LCount) do
Inc(LCount);
ReadLn;
end.
简答:您的代码已损坏
长答案:
对 getter 的调用正在创建一个新数组(参见 System.Generics.Collections.pas
中的 TEnumerable<T>.ToArrayImpl
),该数组在 System.TypInfo.GetDynArrayProp
的尾声中被释放(放置断点在那里查看反汇编程序 - 它显示 @DynArrayClear
)。由于没有其他对该数组的引用,它的内存被释放(如果你进一步进入 System.pas
,你会看到它最终在 _FreeMem
中结束)。这意味着每次调用此函数都会返回一个悬空指针!
现在为什么你在之前的所有调用中都能得到正确的结果?巧合 - 内存没有被其他任何东西重新分配。
想到两个不涉及重写 getter 的可能解决方案:
- 使用来自
System.Rtti.pas
的 RTTI,因为 TValue
保持引用有效
- 编写您自己的
GetDynArrayProp
版本,使引用保持活动状态 - 但您必须确保始终调用 DynArrayClear,否则会造成内存泄漏
我个人会使用第一个。
我在 Delphi 10.2 更新 2 中遇到了一个与 RTTI 相关的问题,并且能够追踪到它的代码量更少(见下文)。
我有一些 TPersistent
-后代 class TMyObj
发布了 属性 类型 TArray<Integer>
。当我通过 GetDynArrayProp()
收到它的值并通过 DynArraySize()
查询它的大小时,这只适用于恰好 649 个元素的大小。在这个特殊计数之上,返回了一些非常大的尺寸值。
请注意,我的数组是从 TDictionary<Integer,Boolean>
的 Keys
属性 的一个实例生成的,它具有自己的 ToArray
方法。
我还尝试修改 TMyObj.GetDynArray
,使其 returns 直接成为 TArray<Integer>
的一个实例,并且它可以正常工作。
因此,我认为这可能以某种神秘的方式相关联。
我使用 DynArraySize()
有什么问题?动态数组的这种神秘行为背后是什么?
program RTTIPropDynArray;
{$APPTYPE CONSOLE}
uses
System.Classes, System.Generics.Collections, System.SysUtils, System.TypInfo;
type
TMyDict = TDictionary<Integer,Boolean>;
TMyArray = TArray<Integer>;
TMyObj = class(TPersistent)
private
FValues: TMyDict;
function GetDynArray: TMyArray;
public
constructor Create(const ACount: Integer);
destructor Destroy; override;
published
property DynArray: TMyArray read GetDynArray;
end;
{ TMyObj }
constructor TMyObj.Create(const ACount: Integer);
begin
FValues := TMyDict.Create;
while FValues.Count < ACount do
FValues.AddOrSetValue(Random(MaxInt), False);
end;
destructor TMyObj.Destroy;
begin
FreeAndNil(FValues);
inherited;
end;
function TMyObj.GetDynArray: TMyArray;
begin
Result := FValues.Keys.ToArray;
end;
function Test(const ACount: Integer): Boolean;
var
LInstance: TMyObj;
LExpectedSize: Integer;
LDynArraySize: Integer;
begin
LInstance := TMyObj.Create(ACount);
try
LExpectedSize := Length(LInstance.DynArray);
LDynArraySize := DynArraySize(GetDynArrayProp(LInstance, 'DynArray'));
Result := LExpectedSize = LDynArraySize;
if not Result then
WriteLn(Format('Expected size: %d; DynArraySize: %d', [LExpectedSize, LDynArraySize]));
finally
LInstance.Free;
end;
end;
var
LCount: Integer;
begin
Randomize;
LCount := 1;
while Test(LCount) do
Inc(LCount);
ReadLn;
end.
简答:您的代码已损坏
长答案:
对 getter 的调用正在创建一个新数组(参见 System.Generics.Collections.pas
中的 TEnumerable<T>.ToArrayImpl
),该数组在 System.TypInfo.GetDynArrayProp
的尾声中被释放(放置断点在那里查看反汇编程序 - 它显示 @DynArrayClear
)。由于没有其他对该数组的引用,它的内存被释放(如果你进一步进入 System.pas
,你会看到它最终在 _FreeMem
中结束)。这意味着每次调用此函数都会返回一个悬空指针!
现在为什么你在之前的所有调用中都能得到正确的结果?巧合 - 内存没有被其他任何东西重新分配。
想到两个不涉及重写 getter 的可能解决方案:
- 使用来自
System.Rtti.pas
的 RTTI,因为TValue
保持引用有效 - 编写您自己的
GetDynArrayProp
版本,使引用保持活动状态 - 但您必须确保始终调用 DynArrayClear,否则会造成内存泄漏
我个人会使用第一个。