Delphi 7,DUnit和FastMM错误地报告了字符串 [英] Delphi 7, DUnit and FastMM reporting Strings incorrectly

查看:108
本文介绍了Delphi 7,DUnit和FastMM错误地报告了字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用DUnit和FastMM捕获未完成的内存块,但是似乎有一个Bug.我不知道它是在FastMM,DUnit还是在Delphi本身,但是在这里:

I'm using DUnit and FastMM to catch unfinalized memory blocks but there seems to be a Bug. I dunno if its in FastMM, DUnit or in Delphi itself, but here goes:

  • 当我的测试用例具有内部字符串时,测试将失败,并发生内存泄漏.如果我再次运行相同的测试而不关闭DUnit GUI,则测试通过OK.出于相同的原因,我相信DUnit GUI测试也是这样.我的应用程序中没有泄漏,证明在这些情况下FastMM不会生成泄漏报告.

  • When my Test Case has internal strings, the test fails with memory leaks. If I run the same test again without closing the DUnit GUI, the test passes OK. The same occours with DUnit GUI Testing, I believe for the same reason. There are no Leaks in my app, the proof is that FastMM doesn't generate the leak report in those cases.

问题1:是否有一种方法可以忽略它们而无需设置AllowedMemoryLeakSize

Question 1: Is there a way to ignore them without setting the AllowedMemoryLeakSize

问题2:如果使用Delphi XE中的此修复程序,我正在使用Delphi 7,有什么消息吗?

Question 2: I'm using Delphi 7, any news if this fix in Delphi XE?

我的实际测试配置:

  • test.FailsOnNoChecksExecuted:= True;
  • test.FailsOnMemoryLeak:=真;
  • test.FailsOnMemoryRecovery:= False;
  • test.IgnoreSetUpTearDownLeaks:= True;

这是示例代码(仅实现)

Here's a sample code (implementation only)

    procedure TTest.Setup;
    begin
        A := 'test';
    end;

    procedure TTest.TearDown;
    begin
        // nothing here :)
    end;

    procedure TTest.Test;
    begin
        CheckTrue(True);
    end;

谢谢!!!

更新: http中记录了我面临的问题://members.optusnet.com.au/mcnabp/Projects/HIDUnit/HIDUnit.html#memoryleakdetection 但是,相同的链接除了再次运行相同的测试外没有其他解决方案.

UPDATE: The problem i'm facing is documented in http://members.optusnet.com.au/mcnabp/Projects/HIDUnit/HIDUnit.html#memoryleakdetection But the same link doesn't present a solution other than running the same test again.

推荐答案

实际上,严格来讲,您的测试第一次运行时会泄漏内存 .
这不是不是 FastMM,DUnit或Delphi中的错误,该错误已在您的测试中.

Actually, strictly speaking your test is leaking memory on the first run.
It's not a bug in FastMM, DUnit or in Delp the bug is in your test.

让我们首先清除误解,并解释一些内部工作原理:

Let's start by clearing up misconceptions, and explaining some inner workings:

这里的问题是,如果FastMM未检测到泄漏,则会给您一种错误的安全感.原因是任何类型的泄漏检测都必须从检查点中查找泄漏.提供在开始"检查点之后由结束"检查点恢复的所有分配-一切都很好.

The problem here is that FastMM can give you a false sense of security if it doesn't detect leaks. The reason is that any kind of leak detection has to look for leaks from checkpoints. Provided all allocations done after the Start checkpoint are recovered by the End checkpoint - everything's cool.

因此,如果您创建一个全局对象Bin,并将所有对象发送到Bin中而不破坏它们,则会这样做发生内存泄漏.像这样继续运行,您的应用程序将耗尽内存.但是,如果Bin在FastMM End检查点之前销毁了所有对象,则FastMM将不会发现任何不愉快的事情.

So if you create a global object Bin, and send all objects to the Bin without destroying them, you do have a memory leak. Keep running like and your application will run out of memory. However, if the Bin destroys all its objects before the FastMM End checkpoint, FastMM won't notice anything untoward.

测试中发生的事情是FastMM的检查点范围比DUnit泄漏检测范围大.您的测试会泄漏内存,但是稍后FastMM进行检查时会恢复该内存.

What's happening in your test is FastMM has a wider range on its checkpoints than DUnit leak detection. Your test leaks memory, but that memory is later recovered by the time FastMM does its checks.

DUnit为每个测试用例创建一个单独的测试类实例.但是,这些实例将在每次测试运行中重复使用.简化的事件顺序如下:

DUnit creates a separate instance of your test class for each test case. However, these instances are reused for each run of the test. The simplified sequence of events is as follows:

  • 开始检查点
  • 呼叫设置
  • 调用测试方法
  • 致电TearDown
  • 结束检查点

因此,如果这三种方法之间存在泄漏-即使泄漏仅发生在实例上,并且在对象被销毁后将立即恢复-也会报告泄漏.在您的情况下,销毁对象时可以恢复泄漏.因此,如果DUnit改为创建&每次运行都会破坏测试类,因此不会报告泄漏.

So if you have a leak between those 3 methods - even if the leak is only to the instance, and will be recovered as soon as the object is destroyed - the leak will be reported. In your case, the leak is recovered when the object is destroyed. So if DUnit had instead created & destroyed the test class for each run, no leak would be reported.

注意这是设计使然,因此您不能真正将其称为错误.

NOTE This is by design, so you can't really call it a bug.

基本上,DUnit对您的测试必须100%独立的原则非常严格.从SetUp到TearDown,必须恢复您(直接/间接)分配的所有内存.

Basically DUnit is being very strict about the principle that your test must be 100% self contained. From SetUp to TearDown, any memory you allocate (directly/indirectly) must be recovered.

无论何时对StringVar := 'SomeLiteralString'StringVar := SomeConstStringStringVar := SomeResourceString进行编码,都会复制常量的值(是,已复制-不计算引用)

Whenever you code StringVar := 'SomeLiteralString' or StringVar := SomeConstString or StringVar := SomeResourceString the value of the constant is copied (yes, copied - not reference counted)

同样,这是设计使然.目的是如果是从库中检索到的字符串,则不是要在如果卸载了库的情况下将该字符串丢弃.因此,它并不是真正的错误,只是一个不方便的"设计.

Again, this is by design. The intention is that if the string was retrieved from a library, you don't that string to be trashed if the library is unloaded. So it's not really a bug, just an "inconvenient" design.

因此,您的测试代码原因在第一次运行时会泄漏内存,原因是A := 'test'正在为测试"副本分配内存.在随后的运行中,将制作另一个测试"副本,并且销毁先前的副本-但净内存分配是相同的.

So the reason your test code leaks memory on the first run is that A := 'test' is allocating memory for a copy of "test". On the subsequent runs, another copy of "test" is made, and the previous copy is destroyed - but the net memory allocation is the same.

在这种情况下,解决方案很简单.

The solution in this particular case is trivial.

procedure TTest.TearDown;
begin
  A := ''; //Remove the last reference to the copy of "test" and presto leak is gone :)
end;

通常,您不必做更多的事情.如果您的测试创建了引用常量字符串副本的子对象,则在销毁子对象时这些副本将被销毁.

And in general, you shouldn't have to do much more than that. If your test creates child objects that reference copies of constant strings, those copies will be destroyed when the child objects are destroyed.

但是,如果您的任何测试将对字符串的引用传递给任何全局对象/单例(顽皮,顽皮,您知道您不应该这样做),那么您将泄漏参考,因此有一些记忆-即使以后可以恢复.

However, if any of your tests pass references to strings to any global objects / singletons (naughty, naughty, you know you shouldn't be doing that), then you'll have leaked a reference and hence some memory - even if it is recovered later.

回到有关DUnit如何运行测试的讨论.同一测试的单独运行可能会相互干扰.例如

Going back to the discussion about how DUnit runs tests. It is possible for separate runs of the same test to interfere with each other. E.g.

procedure TTestLeaks.SetUp;
begin
  FSwitch := not FSwitch;
  if FSwitch then Fail('This test fails every second run.');
end;

扩展这个想法,您可以在第一次和第二次(偶数)运行时进行测试,以泄漏"内存.

Expanding on the idea, you can get your test to "leak" memory on the first and every second(even) run.

procedure TTestLeaks.SetUp;
begin
  FSwitch := not FSwitch;
  case FSwitch of
    True : FString := 'Short';
    False : FString := 'This is a long string';
  end;
end;

procedure TTestLeaks.TearDown;
begin
  // nothing here :(  <-- note the **correct** form for the smiley
end;

这并不会真正导致整体内存消耗的增加,因为每次备用运行都会恢复与第二次运行时泄漏的内存相同的内存.

This doesn't really result in overall consumption of memory increasing because each alternate run recovers the same amount of memory that is leaked on every second run.

字符串复制会导致一些有趣的(也许是意外的)行为.

The string copying results in some interesting (and perhaps unexpected) behaviour.

var
  S1, S2: string;
begin
  S1 := 'Some very very long string literal';
  S2 := S1; { A pointer copy and increased ref count }
  if (S1 = S2) then { Very quick comparison because both vars point to the same address, therefore they're obviously equal. }
end;

但是......

const
  CLongStr = 'Some very very long string literal';
var
  S1, S2: string;
begin
  S1 := CLongStr;
  S2 := CLongStr; { A second **copy** of the same constant is allocated }
  if (S1 = S2) then { A full comparison has to be done because there is no shortcut to guarantee they're the same. }
end;

这确实提出了一种有趣的方法,尽管这种方法极端荒谬,但由于极端荒谬,可能是不明智的建议:

This does suggest an interesting, though extreme and probably ill-advised workaround just due to the sheer absurdness of the approach:

const
  CLongStr = 'Some very very long string literal';
var
  GlobalLongStr: string;

initialization
  GlobalLongStr := CLongStr; { Creates a copy that is safely on the heap so it will be allowed to be reference counted }

//Elsewhere in a test
procedure TTest.SetUp;
begin
  FString1 := GlobalLongStr; { A pointer copy and increased ref count }
  FString2 := GlobalLongStr; { A pointer copy and increased ref count }
  if (FString1 = FString2) then { Very efficient compare }
end;

procedure TTest.TearDown;
begin
  {... and no memory leak even though we aren't clearing the strings. }
end;

最后/结论

是的,显然这篇冗长的文章即将结束.

Finally / Conclusion

Yes, apparently this lengthy post is going to end.

非常感谢您提出问题.
它为我提供了一个有关我记得前一段时间遇到的相关问题的线索.在有机会确认我的理论之后,我将发布问答.一种;因为其他人也可能会觉得有用.

Thank you very much for asking the question.
It gave me a clue as to a related problem I remember experiencing a while back. After I've had a chance to confirm my theory, I'll post a Q & A; as others might also find it useful.

这篇关于Delphi 7,DUnit和FastMM错误地报告了字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆