如何在Delphi中正确释放包含各种类型的记录?

时间:2012-06-16 18:02:05

标签: delphi records

type
  TSomeRecord = Record
    field1: integer;
    field2: string;
    field3: boolean;
  End;
var
  SomeRecord: TSomeRecord;
  SomeRecAr: array of TSomeRecord;

这是我所拥有的最基本的例子,因为我想重用SomeRecord(某些字段保持为空,而不释放所有字段,当我重用SomeRecord时,某些字段会被转移,这显然是不受欢迎的)我正在寻找一种方法来立即释放所有的领域。我开始使用string[255]并使用ZeroMemory(),这很好,直到它开始泄漏内存,这是因为我切换到string。我仍然缺乏了解原因的知识,但它似乎与它的动态有关。我也在使用动态数组,因此我假设在任何动态上尝试ZeroMemory()都会导致泄漏。有一天浪费了这一点。我想我在Finalize()之前使用SomeRecordSomeRecAr之前使用ZeroMemory()解决了这个问题,但我不确定这是正确的方法还是仅仅是我的愚蠢。< / p>

所以问题是:如何一次性释放所有东西?是否存在一些我不知道的单一程序?

另一方面,或者我会建议如何以不同的方式实现这些记录,所以我不需要做出复杂的尝试来释放东西。我已经研究了使用New()创建记录然后摆脱它Dispose(),但我不知道在调用Dispose()之后的变量未定义时它意味着什么,而不是零。另外,我不知道某个类型的变量(SomeRecord: TSomeRecord)与指向类型的变量(SomeRecord: ^TSomeRecord)之间的区别。我正在调查上述问题,除非有人能够快速解释,否则可能需要一些时间。

4 个答案:

答案 0 :(得分:27)

假设您有一个支持在记录上实现方法的Delphi版本,您可以清除这样的记录:

type
  TSomeRecord = record
    field1: integer;
    field2: string;
    field3: boolean;
    procedure Clear;
  end;

procedure TSomeRecord.Clear;
begin
  Self := Default(TSomeRecord);
end;

如果您的编译器不支持Default,那么您可以完全按照以下方式执行相同操作:

procedure TSomeRecord.Clear;
const
  Default: TSomeRecord=();
begin
  Self := Default;
end;

您可能更愿意避免在方法中改变值类型。在这种情况下,创建一个返回空记录值的函数,并将其与赋值运算符一起使用:

type
  TSomeRecord = record
    // fields go here
    class function Empty: TSomeRecord; static;
  end;

class function TSomeRecord.Empty: TSomeRecord;
begin
  Result := Default(TSomeRecord);
end;

....

Value := TSomeRecord.Empty;

顺便说一句,我找不到Default(TypeIdentifier)的任何文档参考。有谁知道它在哪里可以找到?


至于你问题的第二部分,我认为没有理由不继续使用记录,并使用动态数组分配它们。试图自己管理生命周期更容易出错。

答案 1 :(得分:7)

不要让思维过于复杂!

分配“默认”record只会损失CPU功耗和内存。

record内声明TClass时,它会填充零,因此已初始化。当它在堆栈上分配时,只初始化引用计数变量:其他类型的变量(如整数或双精度或布尔值或枚举)处于随机状态(可能非零)。当它将在堆上分配时,getmem将不会初始化任何内容,allocmem将填充所有内容为零,new将仅初始化引用计数成员(如在堆栈初始化时) ):在所有情况下,您应使用disposefinalize+freemem来释放堆分配的record

关于您的确切问题,您自己的假设是正确的:要在使用后重置记录内容,请不要使用“fillchar”(或“zeromemory”)而不使用之前的“{{1} }”。这是正确和最快的方式:

finalize

再次,它将比分配默认记录更快。并且在所有情况下,如果您使用Finalize(aRecord); FillChar(aRecord,sizeof(aRecord),0); ,甚至多次,它将不会泄漏任何内存 - 100%退款保修!

编辑:查看Finalize生成的代码后,代码已经过优化:实际上它是aRecord := default(TRecordType) + Finalize个模仿stosd。因此,即使语法是副本/分配(FillChar),也不会将其实现为副本/赋值。我的错误在这里。

但我仍然不喜欢必须使用:=这一事实,其中Embarcadero应该更好地使用像:=这样的record方法作为语法,就像{{3} }}。实际上,这个aRecord.Clear语法与C#使用的语法完全相同。听起来如果Embacardero在任何地方模仿C#语法,却没有发现DelphiWebScript's dynamic arrays。如果德尔福只是一个追随者,而不是实施者认为“它的方式”是什么意思呢?人们总是更喜欢原始的C#到它的祖先(德尔福有同样的父亲)。

答案 2 :(得分:5)

我想到的最简单的解决方案是:

const
  EmptySomeRecord: TSomeRecord = ();
begin
  SomeRecord := EmptySomeRecord;

但要解决问题的所有其余部分,请采用以下定义:

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    Field1: Integer;
    Field2: String;
    Field3: Boolean;
  end;
  TSomeRecords = array of TSomeRecord;
  PSomeRecordList = ^TSomeRecordList;
  TSomeRecordList = array[0..MaxListSize] of TSomeRecord;    
const
  EmptySomeRecord: TSomeRecord = ();
  Count = 10;    
var
  SomeRecord: TSomeRecord;
  SomeRecords: TSomeRecords;
  I: Integer;
  P: PSomeRecord;
  List: PSomeRecordList;

procedure ClearSomeRecord(var ASomeRecord: TSomeRecord);
begin
  ASomeRecord.Field1 := 0;
  ASomeRecord.Field2 := '';
  ASomeRecord.Field3 := False;
end;

function NewSomeRecord: PSomeRecord;
begin
  New(Result);
  Result^.Field1 := 0;
  Result^.Field2 := '';
  Result^.Field3 := False;
end;

接下来是一些关于如何操作它们的例子:

begin
  // Clearing a typed variable (1):
  SomeRecord := EmptySomeRecord;

  // Clearing a typed variable (2):
  ClearSomeRecord(SomeRecord);

  // Initializing and clearing a typed array variabele:
  SetLength(SomeRecords, Count);

  // Creating a pointer variable:
  New(P);

  // Clearing a pointer variable:
  P^.Field1 := 0;
  P^.Field2 := '';
  P^.Field3 := False;

  // Creating and clearing a pointer variable:
  P := NewSomeRecord;

  // Releasing a pointer variable:
  Dispose(P);

  // Creating a pointer array variable:
  ReallocMem(List, Count * SizeOf(TSomeRecord));

  // Clearing a pointer array variable:
  for I := 0 to Count - 1 do
  begin
    Pointer(List^[I].Field2) := nil;
    List^[I].Field1 := 0;
    List^[I].Field2 := '';
    List^[I].Field3 := False;
  end;

  // Releasing a pointer array variable:
  Finalize(List^[0], Count);

根据需要选择和/或组合。

答案 3 :(得分:1)

使用SomeRecord: TSomeRecordSomeRecord将是TSomeRecord类型的实例/变量。对于SomeRecord: ^TSomeRecordSomeRecord将是指向TSomeRecord类型的实例或变量的指针。在最后一种情况下,SomeRecord将是一个类型指针。如果您的应用程序在例程之间传输大量数据或与外部API交互,则建议使用类型指针。

new()dispose()仅用于键入指针。对于类型指针,编译器不具有应用程序使用此类变量的内存的控制/知识。由你来释放类型指针使用的内存。

另一方面,当你使用普通变量时,根据使用和声明,编译器会在它认为不再需要时释放它们使用的内存。例如:

function SomeStaff();
var
    NativeVariable: TSomeRecord;
    TypedPointer: ^TSomeRecord;
begin
    NaviveVariable.Field1 := 'Hello World';

    // With typed pointers, we need to manually
    // create the variable before we can use it.
    new(TypedPointer);
    TypedPointer^.Field1 := 'Hello Word';

    // Do your stuff here ...

    // ... at end, we need to manually "free"
    // the typed pointer variable. Field1 within
    // TSomerecord is also released
    Dispose(TypedPointer);

    // You don't need to do the above for NativeVariable
    // as the compiler will free it after this function
    // ends. This apply also for native arrays of TSomeRecord.
end;

在上面的示例中,变量NativeVariable仅在SomeStaff函数中使用,因此编译器会在函数结束时自动释放它。这适用于几乎大多数本机变量,包括数组和记录“字段”。对象的处理方式不同,但这是另一篇文章。