【问题标题】:How to leak a string in Delphi如何在 Delphi 中泄漏字符串
【发布时间】:2009-07-15 16:45:23
【问题描述】:

前几天我正在和一位同事讨论如果你真的把事情搞砸了如何在 Delphi 中泄漏一个字符串。默认情况下,字符串是引用计数并自动分配的,因此它们通常可以不假思索地工作——无需手动分配、大小计算或内存管理。

但我记得曾经读过有一种方法可以直接泄漏字符串(不将其包含在泄漏的对象中)。似乎它与通过引用传递字符串然后从传递到的例程中从更大范围内访问它有关。是的,我知道这很模糊,这就是我在这里问这个问题的原因。

【问题讨论】:

    标签: delphi memory-leaks string


    【解决方案1】:

    我不知道你第二段中的问题,但是我被记录中的泄漏字符串咬了一次。

    如果您在包含字符串的记录上调用 FillChar(),您会用零覆盖引用计数和动态分配内存的地址。除非字符串为空,否则这将泄漏内存。解决这个问题的方法是在清除它占用的内存之前在记录上调用 Finalize()

    不幸的是,当没有需要终结的记录成员时调用 Finalize() 会导致编译器提示。我碰巧注释掉了 Finalize() 调用以使提示静音,但后来当我向记录添加字符串成员时,我错过了取消注释调用,因此引入了泄漏。幸运的是,我通常在调试模式下最冗长和偏执的设置中使用 FastMM 内存管理器,因此泄漏并没有被忽视。

    编译器提示可能不是一件好事,如果不需要,默默地忽略 Finalize() 调用会更好恕我直言。

    【讨论】:

    • > 如果不需要的话,默默地忽略 Finalize() 调用会更好恕我直言+1
    • 你知道短字符串是否受此影响吗?
    • @JamesB:不,短字符串不受此影响,因为它们没有引用计数并且不包含指向可能丢失的内存块的指针。短字符串由单个长度字节和字符数据组成,因此用0s 填充它们只需将长度设置为0,并将所有元素设置为#0
    • 我以为是这样......我们的代码中都有短字符串记录(真的很烦人,因为它们不够大,数据不断被截断),我一直想知道为什么...... . 我敢打赌这就是原因。
    【解决方案2】:

    不,我不认为这样的事情会发生。字符串变量可能会获得一个您没有预料到的值,但它不会泄漏内存。考虑一下:

    var
      Global: string;
    
    procedure One(const Arg: string);
    begin
      Global := '';
    
      // Oops. This is an invalid reference now. Arg points to
      // what Global used to refer to, which isn't there anymore.
      writeln(Arg);
    end;
    
    procedure Two;
    begin
      Global := 'foo';
      UniqueString(Global);
      One(Global);
      Assert(Global = 'foo', 'Uh-oh. The argument isn''t really const?');
    end;
    

    这里One 的参数被声明为 const,所以据说它不会改变。但随后One 通过更改实际参数而不是形式参数来规避这一点。过程 Two“知道”One 的参数是 const,因此它希望实际参数保持其原始值。断言失败。

    字符串没有泄露,但这段代码确实演示了如何获取字符串的悬空引用ArgGlobal 的本地别名。尽管我们更改了GlobalArg 的值保持不变,并且因为它被声明为 const,所以字符串的引用计数在进入函数时不会增加。重新分配Global 将引用计数降至零,并且字符串被销毁。将Arg 声明为 var 也会有同样的问题;按值传递它可以解决这个问题。 (对UniqueString 的调用只是为了确保字符串是引用计数的。否则,它可能是非引用计数的字符串文字。)所有编译器管理的类型都容易受到这个问题的影响;简单类型是免疫的。

    泄漏字符串的唯一方法是将其视为字符串以外的东西,或者使用非类型感知的内存管理函数。 Mghie's answer 描述了如何通过使用 FillChar 破坏字符串变量来将字符串视为字符串以外的东西。非类型感知内存函数包括GetMemFreeMem。例如:

    type
      PRec = ^TRec;
      TRec = record
        field: string;
      end;
    
    var
      Rec: PRec;
    begin
      GetMem(Rec, SizeOf(Rec^));
      // Oops. Rec^ is uninitialized. This assignment isn't safe.
      Rec^.field := IntToStr(4);
      // Even if the assignment were OK, FreeMem would leak the string.
      FreeMem(Rec);
    end;
    

    有两种方法可以修复它。一种是拨打InitializeFinalize

    GetMem(Rec, SizeOf(Rec^));
    Initialize(Rec^);
    Rec^.field := IntToStr(4);
    Finalize(Rec^);
    FreeMem(Rec);
    

    另一种是使用类型感知函数:

    New(Rec);
    Rec^.field := IntToStr(4);
    Dispose(Rec);
    

    【讨论】:

    • AFAICS Global 示例适用于任何变量类型,因此这可能不是 Jim 所追求的。
    • 啊,你是对的。但作为一个字符串,它可能导致其他问题,正如我将在编辑此答案时演示的那样。
    【解决方案3】:

    实际上,在 Delphi 2007 和 2009 中,将字符串作为 CONST 或非 const 传递在引用计数方面是相同的。当字符串作为 CONST 传递时,曾出现过导致访问冲突的情况。这是问题之一

    type
      TFoo = class
        S: string;
        procedure Foo(const S1: string);
      end;
    
    procedure TFoo.Foo(const S1: string);
    begin
      S:= S1; //access violation
    end;
    
    var
      F: TFoo;
    begin
      F:= TFoo.create;
      try
        F.S := 'S';
        F.Foo(F.S);
      finally
        F.Free;
      end;
    end.
    

    【讨论】:

    • 这看起来有点像我在想的,但它不是 2009 年的 AV,我相信这就是你所说的。
    • 它不是 AV,因为在 Delphi 2009 中,如果 $STRINGCHECKS 为 ON,“const”会失去其功能。
    【解决方案4】:

    另一种泄漏字符串的方法是将其声明为 threadvar 变量。有关详细信息,请参阅my question。有关解决方案,请参阅the solution on how to tidy it

    【讨论】:

      【解决方案5】:

      我认为this 可能与我的想法相似。它是字符串泄漏的反面,一个早期收集的字符串:

      var
        p : ^String;
      
      procedure InitString;
      var
        s, x : String;
      begin
        s := 'A cool string!';
        x := s + '. Append something to make a copy in' +
                   'memory and generate a new string.';
      
        p := @x;
      end;
      
      begin
        { Call a function that will generate a string }
        InitString();
      
        { Write the value of the string (pointed to by p) }
        WriteLn(p^); // Runtime error 105!
      
      
        { Wait for a key press }
        ReadLn;
      end.
      

      【讨论】:

      • 您有一个指向堆栈上超出范围的对象的指针。你能指望什么???不管是不是把弦清理干净了这都可能吹爆。
      • Loren,您期望一切都按照您的预期工作,无论您编写的实际代码如何!现在的编译器是通灵的,不是吗?
      • 这里必须同意 Loren 的观点。第二次调用 @ 符号,所有赌注都被取消。您正在向编译器礼貌地告别所有类型安全和引用计数机制,并将原始内存掌握在自己手中。
      • 是的,这个很明显。我会继续寻找,但我可能记错了或者它已经修复了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-30
      • 2011-08-29
      • 2013-04-03
      • 2011-06-12
      • 2018-12-17
      • 2013-12-03
      • 2011-05-04
      相关资源
      最近更新 更多