【问题标题】:Dynamic arrays and memory management in DelphiDelphi中的动态数组和内存管理
【发布时间】:2014-02-28 03:57:55
【问题描述】:

下面关于 Delphi 中动态数组的文章说您使用 SetLength() 函数分配动态数组。

myObjects : array of MyObject;
...
SetLength(myObjects, 20);
// Do something with the array.
myObjects := nil;

http://delphi.about.com/od/beginners/a/arrays.htm

这对我来说似乎是内存泄漏:

问题是,如果SetLength()相当于C++中的MyObject *obs = new MyObject[20],那么数组就是指针,所以将Delphi的myObjects变量设置为nil与在 C++ 中设置obj = NULL?即,这是内存泄漏吗?

编辑:我从 David 的回答中了解到编译器为动态分配的数组管理内存。我也从他的回答中了解到,编译器确实为普通类实例管理内存(因此使用myObj := MyObject.CreatemyObj.FreemyObj := nil 等)。此外,因为 Delphi 类(不是记录)总是在堆上分配(Delphi 使用一种引用/指针系统),这是否意味着(自动内存管理的)动态数组中的所有对象仍然需要内存 -由我管理?例如,以下是否通过双重释放结果导致错误?

myObjects : array of MyObject;
...
SetLength(myObjects, 20);
for i := 0 to 19 do
begin
  myObjects[i] := MyObject.Create;
end;
// Do something with the array.
// Before de-allocating it, if I *know* I am the only user of the array,
// I have to make sure I deallocate each object.
for i := 0 to 19 do
begin
  myObjects[i].Free;
  myObjects[i] := nil; // Redundant, but for illustrative purposes.
end;
myObjects := nil;

【问题讨论】:

    标签: c++ arrays delphi pointers


    【解决方案1】:

    动态数组由编译器管理。这是通过维护对数组的所有引用的引用计数来完成的。当对数组的最后一个引用被分离时,数组被释放。

    documentation 说:

    动态数组变量是隐式指针,并由用于长字符串的相同引用计数技术管理。要释放动态数组,请将 nil 分配给引用数组的变量或将变量传递给 Finalize;只要没有其他对它的引用,这些方法中的任何一个都会处理数组。动态数组在引用计数降至零时自动释放。长度为 0 的动态数组的值为 nil。不要将取消引用运算符 (^) 应用于动态数组变量或将其传递给 New 或 Dispose 过程。

    在您的示例中,将nil 分配给您的变量会分离唯一的引用并导致数组被释放。所以没有泄漏。

    Delphi 动态数组与 C++ new 非常不同。与 Delphi 中最接近的类似物是 GetMemNew 的原始内存分配。


    您的编辑提出了不同的问题。类的实例不受管理。它们必须被明确地释放。你的代码就是这样做的。没有双重释放,因为编译器不管理类的实例。

    【讨论】:

    • 那么您是说 Delphi 通过使用引用计数对数组进行基本的垃圾收集? (我假设编译器为保存引用计数的整数保留了几个字节。)如果 Delphi 有用于动态数组的 GC,为什么它也没有用于类的 GC?
    • @user 基本上是的。虽然这是确定性引用计数而不是 GC。引用计数(和长度)存储在数组有效负载之前的元数据记录中。对象早于 dyn 数组。移动编译器使用 ARC 引用计数对象。
    • 是的,我通过 Objective-C 熟悉了 ARC。
    • 好的。将动态数组视为 Obj-C 中的 ARC 对象。 Delphi 接口的管理方式相同。和字符串一样。
    • 顺便说一下,我知道我的编辑提出了不同的问题。但是,我最近才发现TStringList 类有一个OwnsObject 属性,该属性确定在销毁TStringList 实例时是否自动释放包含的对象,我不确定这种模式是否适用于动态数组也是如此。我需要澄清您编辑的答案提供的这两点。再次感谢您。
    【解决方案2】:

    将对象引用设置为nil 不会释放它。它只是减少内部引用计数。当该引用计数达到 0 时,内存被释放或可供释放。

    这样做:

    Obj.Free
    Obj := nil;
    

    根本不会释放它两次。它将释放它一次并将 Obj 指针设置为 null。

    对于字符串,引用计数通常存储在第一个元素之前 2 个字的偏移处,而大小存储在第一个元素之前 1 个字的位置。如果它是常量,则引用计数通常为 -1。不确定是否仍然如此。

    【讨论】:

    • 请用“对象”定义你的意思
    • 你误解了我的第二个问题。我不是在问“obj.Freeobj := nil 是否会双倍免费?”。我在问“myObjs := nil(假设myObjs 指向的对象的引用计数为1)会自动在数组中包含的每个对象上调用Free 吗?”。
    • @user1420752 是和否。当您的编译目标是移动平台时,将 NIL 分配给对象引用将减少引用计数,一旦达到零,内存将被释放。这称为 ARC(自动引用计数)。在针对桌面平台 (Win32/MacOS) 时,没有对象引用的自动引用计数,因此您需要自己释放对象。如果您为多个目标编译,只需调用 Free,它就会在移动和桌面应用程序中正常工作。
    • @user1420752 你在问数组中的每个索引是否是堆上的一个对象,如果释放数组时它会被释放?答案是否定的,因为对该索引处的对象的引用可以大于 1,但对数组本身的引用可以为 0 并将被释放。同样,数组将被释放,但不会释放其每个索引,除非数组中的每个对象的引用计数也为 0。
    • @CantChooseUsernames:您所说的仅适用于移动平台,其中 ARC 确实用于对象。它不适用于不将 ARC 用于对象的桌面平台。您需要做出区分,这非常重要。
    猜你喜欢
    • 2021-12-05
    • 2020-08-08
    • 2011-11-14
    • 2016-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多