Memory leaks 使用';指针';参数

Memory leaks 使用';指针';参数,memory-leaks,freepascal,fpc,compiler-bug,Memory Leaks,Freepascal,Fpc,Compiler Bug,在用ToBytes方法(见下文)替换对TBytes(字符串数组)的转换的硬类型转换后,Delphi报告没有内存泄漏-Free Pascal 2.6.2但是在TBytes值被传递给带有指针类型的参数的方法时显示了泄漏 以下代码泄漏内存: program project1; {$mode delphi} uses SysUtils; function ToBytes(const AValue: AnsiString): TBytes; begin SetLength(Result, L

在用ToBytes方法(见下文)替换对TBytes(字符串数组)的转换的硬类型转换后,Delphi报告没有内存泄漏-Free Pascal 2.6.2但是在TBytes值被传递给带有
指针类型的参数的方法时显示了泄漏

以下代码泄漏内存:

program project1;

{$mode delphi}

uses
  SysUtils;

function ToBytes(const AValue: AnsiString): TBytes;
begin
  SetLength(Result, Length(AValue)); // <-- leak (ine 10)
  if Length(AValue) > 0 then
    Move(AValue[1], Result[0], Length(AValue));
end;

procedure Send(P: Pointer);
begin

end;

begin
  Send(ToBytes('test'));

  SetHeapTraceOutput('heaptrace.log');
end. 

如果我将Send方法更改为接受TBytes类型的参数,内存泄漏就会消失。

这是一个编译器错误。托管类型
TBytes
具有引用计数生存期。编译器应该创建一个隐式局部变量,该变量分配给
ToBytes
返回的数组。您需要通过将以下内容存储到显式本地

var
  Tmp: TBytes;
....
Tmp := ToBytes(...);
Send(Tmp);

返回可能包含大量内存的内容的函数让我感到恶心。这可能只是一种感觉或个人偏好,但它确实剥夺了对内存分配的控制。关于返回(新构造的)TStringList实例的函数,也有类似的说法,建议传递一个指向TStrings对象的指针,并让调用者控制对象的生命周期


在这里,我建议对TBytes上的所有操作使用var参数,并强制调用方提供一个要处理的实例。如果您构建了一个复杂的应用程序,必须搜索可以提高性能的地方,请再次考虑这一点,看看是否可以回收内容或长度相似的TBytes实例。Delphi字符串系统的一个重要功能是在后台为您执行引用计数和写时复制,当您的应用程序处理大量(类似)字符串时,可以提供这种性能提升。

这可能不是您认为的错误。FPC的堆跟踪在主程序(main.dpr begin..end)中跟踪temp(以及通常的自动类型)方面存在已知问题

将代码移动到过程中,并从主begin..end调用该过程。你会看到漏洞消失了

这是因为主程序的总体结构如下

begin 
  initializeunits(); // procedure call inserted by the compiler
    <actual mainprogram statements>
  finalizeunits();   // procedure call inserted by the compiler
end.
开始
初始化Units();//编译器插入的过程调用
finalizeEUnits();//编译器插入的过程调用
结束。

随着主程序temp的发布发生在“end.”结束后,完成堆跟踪的单元。(即使它是第一个单位,它仍然只是一个单位)。因此heaptrc忽略了这一点。

这更多的是一个评论,而不是对所问问题的回答。您将重点关注一个问题,即堆分配不理想对性能的影响。还有另一个问题,那就是可组合性。返回值的函数可以用作另一个函数的参数。或赋值语句的右侧。另一方面,通过
var
参数返回的过程需要多个单独的语句,以及可能不需要的局部变量赋值。TBCSo,如果您关心代码的可读性和清晰度,那么返回值总是首选的,因为它们是实用的。例如,我的代码库中有许多数字类型。一种常用的类型是包含3个实元素的向量。这是声明为记录的,许多函数返回它,我广泛使用运算符重载。您可以像使用内置类型一样使用它,如
Integer
Double
string
等。代码总是感觉正确。另一方面,我有一个只有在运行时才知道维度的向量类型。这意味着元素需要存在于堆上。他们中可能有很多人。因此,性能成为一个真正的问题。我真的不想执行比需要更多的堆分配。对于此类,对该类型进行操作的方法都是由调用方构造的已传递的现成实例。这满足了性能需求,但代码无法读取!是的,我第一次想把它作为一个评论发布,但是因为它也可以解决内存泄漏问题,所以我把它作为一个答案发布了。我了解你的可组合性,但如果你想更易于使用,我会提出“制作一个合适的对象”。(3个real的内存不是那么多,也不是可变长度的)。关于您提到的这个类,为什么方法不在对象本身上操作?我还读了很多关于返回Self的函数,因此可以进行链接(这会导致非常可读的tripe IMHO),还有其他方法可以保持代码简洁易读。三雷亚尔并不多。它们是值,所以函数返回值工作得很好。为什么这些方法不能对对象本身进行操作?他们是。但这就是代码无法读取的原因。我真正想要的是能够使用赋值和运算符。我可以用一张保存着dyn数组的唱片,然后是COW。但是表演阻止了我。这个问题()我可以实现,但是性能不好。是的,在我看来,流畅的界面通常是不可读的。编译器肯定有问题。如果我删除TBytes声明并使用SysUtils(定义了SysUtils),我会在
端得到一个SIGSEGV。
@StefanGlienke我尝试将代码从顶层
开始端移到函数中。
在进程启动时得到一个SIGSEV。奇怪,似乎和heaptrc单位有关<代码>程序项目1;使用SysUtils、heaptrc;开始-结束。
=>SIGSEGVOk,我的坏,heaptrc需要成为第一个单元。把它放在那里或使用-gh开关都可以。@StefanGlienke非常感谢你对SysUtils.TBytes的提示,我已经更新了免费Pascal/Lazarus Bug Tracker中报告的代码示例,将代码移动到一个过程中,并从主开始调用它。。。end没有修复漏洞(我在bug tracker项中添加了一条带有漏洞报告的注释)
begin 
  initializeunits(); // procedure call inserted by the compiler
    <actual mainprogram statements>
  finalizeunits();   // procedure call inserted by the compiler
end.