小弟我已经开始怀疑小弟我碰到的这个有关问题是否是Delphi的巨大Bug了,涉及内存

我已经开始怀疑我碰到的这个问题是否是Delphi的巨大Bug了,涉及内存!
我的模块是对通讯消息进行解码,大概流程是这样的:
当接受一条消息时,我先将解码结果临时保存在一个全局变量(类型为动态的字符串数组)中,然后创建一个对象(专门用来存储结果的类),此对象有一个属性,类型也为动态的字符串数组,通过对象的方法将解码内容保存在这个属性里面。
我现在碰到的现象就是,在我将解码结果保存到这个属性之前,内存增长正常,而一旦我将解码结果保存到对象中,内存就以1M/分钟的速度增长;为此我已经进行过很多测试,Memproof没有发现内存泄露,而解码结果本身也就是动态字符串数组的大小也是很小的。但就是不知道多出来的内存是何处增加的。
大家甚至可以自己做一个测试。

------解决方案--------------------
不是编译器的问题,应该是Object Pascal中string 类型的内存分配问题。
罪魁祸首当然是TStringArray=array of string;

查看一下文档,String类型在内存中的确切格式如下:

(4字节)分配大小+(4字节)引用计数+(4字节)字串长度+(不定长)字符数组+(1字节)$0结束字符

经过别人的测试,实际的空间分配:字串长度+17

我测试一下确实暴涨内存,但换成PChar类型后没正常。

字串长度+17


unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls;

type
TStringArray=array of PChar;

TArray=class
private
FArr:TStringArray;
public
procedure AddValue(A:TStringArray);
end;

TForm1 = class(TForm)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

{ TArray }

procedure TArray.AddValue(A: TStringArray);
begin
FArr:=A;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
A:TStringArray;
i:Integer;
begin
// Timer Interval:=60ms
SetLength(A,50);
for i:=0 to 49 do A[i]:=PChar( '1234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ');
with TArray.Create do
AddValue(A);
end;

end.


------解决方案--------------------
另外,HsWong()说的也对,不过还不完全对。

procedure TForm1.Timer1Timer(Sender: TObject);
var
A:TStringArray;
i:Integer;
begin
// Timer Interval:=60ms
SetLength(A,50);
for i:=0 to 49 do A[i]:=PChar( '1234567890aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa '); // 这里是在引用一个const,对const本身没有写动作。
with TArray.Create do
AddValue(A); // 这里记录进去的,只是一个引用,只增加一次引用计数。因为对const本省没有写动作。
end;

//=====================

先看第一次Timer1的OnTimer的执行:
procedure TForm1.Timer1Timer(Sender: TObject);
var
i:integer;
begin
setLength(a,50); // <-- a的尺寸被定为50,每个a[i]引用计数是0,size是0。
for i:=0 to 49 do
a[i]:= char(i); // <-- 每个a[i]都有一个写动作,分配了内存,引用计数是1。
DecodeResult:=TDecodeResult.Create ;
DecodeResult.AddValue(a); // <-- copy走了50个指针。每个a[i]的引用计数变成2了,不过没有写动作,只是记录了a[i]的指针。
end;

再看第二次Timer1的OnTimer的执行:
procedure TForm1.Timer1Timer(Sender: TObject);
var
i:integer;
begin
setLength(a,50);
for i:=0 to 49 do
a[i]:= char(i); // <-- 每个a[i]都有一个写动作,copy-on-write起作用了哦,重新分配了内存并写入新值,就是说a[i]变成a[i] '了。原来的那一块a[i],引用计数减1,又是1了,留着给上一次的DecodeResult用了。
DecodeResult:=TDecodeResult.Create ;
DecodeResult.AddValue(a); // <-- 这次copy走的可是50个a[i] '了。
end;