请教个delphi关于占用和释放内存的问题 ( 积分: 50 )

  • 主题发起人 主题发起人 popogens
  • 开始时间 开始时间
P

popogens

Unregistered / Unconfirmed
GUEST, unregistred user!
本人编了个用于打印的软件,由于考虑到RichEdit、RichEdit2功能不够强大,而delphi控制word方法太复杂,因而采用了折衷方法:用TMemo生成文本,用特定的命领在TMemo里读入、修入内容,另存为htm文件,再打开它。
别看方法怪,可是挺实用。
但新的问题产生了:多次调用时会产生错误,经多次检查,没发现哪里程序有错,而且在单位电脑上(配置较差)重复试3-4次会产生错,在家里电脑上,一般是第8次产生错误,我怀疑是内存被占用过多,在家里重复执行了6-7次,用内存整理工具整理一次,再执行6次,再整理……始终没发生错误。确定是占用内存过多。
PS:我还用了很多个Tinifile控件

现在我想请教一下:哪些东西比较占内存并且不被释放?这个我不太了解,看来以后得注意了,还有,Delphi里有什么方法能释放一些不被使用的内存吗?
 
一般情况你要使用对象变量,用完之后,要释放掉。
var
Ainifile:Tinifile;
bagin
Ainifile:=Tinifile.cerate;
{.......}

Ainifile.free;//这个就释放掉了
end;
=======
有比如:
var
F:TForm1;
bagin
F:=TForm1.cerate(nil);
{...}
F.Free;
end;

而这个:
var
F:TFrom1;
begin
F:=TForm1.cerate(self);
{.........}
//就不用F.free;
end;
 
要看看程序是否占用太多内存,用 任务管理器 看看程序的物理内存和虚拟内存的占用情况就会知道了。
一般来说手工 Create 的类都需要在适当的地方手工 Free 掉来释放内存。
var
F: TIniFile;
begin
F := TIniFile.Create('...');
try
......
finally
F.free
end;
end;
 
form和tinifile还没到释放的时候,接下去还要用,而按照tinifile的读写方式,我认为也直接写到了硬盘上不会占内存。
我就是想不出来为什么会出现:
"0x00402200"指令引用的"0x00e34c0c"内存。该内存不能为"written"。
我反复使用的过程大体上由:读入txt文件,其中有一些预留被找到并被替换的地方,进行大量内部的文本处理,将文本处理的结果替入,组成一个将要被另存的文本文件内容。
 
说老实话,我没有看明白楼主想要说明什么?
 
搂主:
我也没有弄明白你要问的问题!!!
我再提问你一句:
如果:
你反复调用某一个过程里面的
Ainifile:= TIniFile.Create('...');
那就是在不断占用内存;
比如:
procedure A;//建立了对象的过程
var
Ainifile:Tinifile;
bagin
Ainifile:=Tinifile.cerate;
{.......}
end;
调用:
begin
A;
Ainifile.Max;
//.....
end;
如果你调用了,5次内存就填满了。你改成:
建立一个全局变量:
var
Ainifile:Tinifile;
在OnCreate里建立,或者OnShow事件里
begin
Ainifile:=Tinifile.cerate;
end;
然后,在调用的时候访问属性:
begin
A;
Ainifile.Max;
...
end;
最后
在OnClose里释放掉:
begin
Ainifile.Free;
end;
这样你调用100次1000次10000次A过程用来访问属性Ainifile.Max;那么
Ainifile:=Tinifile.cerate;的对象的建立,你只占用了一次内存。
你就可以节省许多内存。
看看你是不是我说的这种情况。
要不,把你的源码贴上来看看,分析分析。
 
我在form建立的时候把tinifile.create了(很多个Tinifile)
close的时候相信tinifile和form一起被释放掉了

我反复进行的操作是:
1、“打开”按钮:{读入一个txt文件,并从ini中读入很多内容,对txt文本进行局部修改}
2、“关闭”按钮:{把txt文本释放掉}

PS:我做的一个不小的软件,源码涉及东西太多太长了,即使你是D高手,相信看明白也得花个几天时间哦。
 
前者你要是调用5次A过程就会
Ainifile:=Tinifile.cerate;
Ainifile.Max;
二者占用5次内存。
而后者
Ainifile:=Tinifile.cerate;
只占用1次内存;
这个Ainifile.Max;占用5次内存。
看看是那个划算!!!
 
>>1、“打开”按钮:{读入一个txt文件,并从ini中读入很多内容,对txt文本进行局部修改}
1、我觉得你在用完Ainifile:=Tinifile.cerate;即Ainifile.Max(Max是我打的比方)之后,就应该释放Ainifile.Free;
2、如果每次打开按钮都用Ainifile哪个机器能吃的销?
3、如果是内存占用太多,肯定是你要优化你的代码。
4、大多数的吃掉太多内存的程序多半是代码优化不好。
 
procedure TWrits.BMakeClick(Sender: TObject);
var
BName, SName, SFile: String;
begin
if CurrentTemplate = '' then Showmessage('请先选择一个要制作的文书!')
else
begin
BName := '';
BName := GetBroadName(CurrentTemplate);
if BName = '' then BName := CurrentTemplate;
if BName = CurrentTemplate then SName := '' else SName := RightStr(CurrentTemplate, Length(CurrentTemplate) - Length(BName) - 2);
SFile := ExePath + 'Writ/Templates/' + BName;
if not FileExists(SFile + '.WRS') then Showmessage('该文书:“' + BName + '”模板不存在!必须先制作好模板,才能用模板制成文书。')
else if Application.MessageBox('点击“确定”以继续制作此文书。', '制成文书', Mb_yesno + Mb_IconQuestion) = idyes then
begin
Broad.M.Lines.LoadFromFile(SFile + '.WRS');
if SName = '' then MakeSpace
else
begin
try Broad.IniSub.Free; except end;
Broad.IniSub := Tinifile.Create(SFile + '.Sub');
if not Broad.IniSub.SectionExists(SName) then
begin
if Application.MessageBox(PChar(' 文书模板存在,但对应的子模板不存在,如需制成有对应子模板设定项目的文书,请先建立并设定该子模板。' + #10 + ' 是否制成空文书?'), '子模板不存在', Mb_yesno + Mb_IconInformation) = idyes then MakeSpace;
end
else CreateSub(BName, SName);
end;
end;
end;
end;

//我用BName表示模板文件名,SName表示子该模板的一个子模板名称,方便阅读注释一下。

这里主要调用了一个CreateSub(BName, SName);过程,也就是找到模板并存在对应子模板的情况下的过程,如下:

procedure TWrits.CreateSub(BName, SName: String);
var
I, J, SubS: Integer;
BooG: Boolean;
S: String;
begin
//打开并读取子板各属性
try Broad.IniSubProperty.Free; except end;
Broad.IniSubProperty := Tinifile.Create(ExePath + 'Writ/Templates/' + BName + '.Sbp');
SubS := Broad.IniSubProperty.ReadInteger(SName, 'S', 16);
BooG := Broad.IniSubProperty.ReadBool(SName, 'G', false);
if BooG then SGoods := InputBox('有关财物', '该文书指向的有关财物: ', '');//打开物窗
if (SGoods = '') and BooG then
begin
Showmessage('有财物指向的文书必须输入相关财物。');
Abort;
end;
Broad.DetailCount := 0;
for I := Broad.M.Lines.Count - 1 downto 0 do
begin
S := Broad.M.Lines.Strings;
if LeftStr(S, 4) = '<!--' then
begin
for J := 4 to Length(S) do
begin
if MidStr(S, J, 3) = '-->' then
Break;
end;
S := MidStr(S, 5, J - 5);
S := Broad.IniSub.ReadString(SName, S, '');
Broad.M.Lines.Strings := RightStr(Broad.M.Lines.Strings, Length(Broad.M.Lines.Strings) - J - 2);//把头注释去掉
if (NotCaseScript(LowerCase(LeftStr(S, 4)))) or (Main.CurrentName > '') then
begin
AppWantReturn := 1;
try StrCut.ExeScript(S, '');
finally DoScript(I, SubS, S, BName);//内容已在MValue里,要逐行处理
end;
end;
end;
end;
I := Broad.IniBroads.ReadInteger(BName, 'allwaysdel', 0);
if Broad.DetailCount > I then Broad.DetailCount := Broad.DetailCount - I;
BeforeP(BName, SName, SubS);
try Broad.IniSubProperty.Free; except end;
end;

//这里再说明一下ExeScript过程是在之前最基础的过程中在另一个StrCut窗体中建的一个专门处理特定格式的文本的过程,通过调试和在其他地方应用,应该没问题:它的功能比如将“{cm}当事人基本情况”转换为txt文件中标注“当事人基本情况”所在的整个段落。
//DoScript(I, SubS, S, BName)比较关键,很可能问题就在此,它的功能是将ExeScript处理后所得的结果比如“当事人基本情况”(在StrCut.MValue中)填到txt文本中对应位置,并打开一个完成txt文件修改之前的小窗口(该窗口在确实之前还有小部分修改工作)。
//该小窗口无论确定还是关闭都不会发生问题,而在多次点击“制作”按钮,也就是多次调用了BMakeClick(Sender: TObject);过程,就会发生错误,错误时常不同,除了上述一程错误,另一种也是关于内存的提示,还有一种严重错误就是直接退出程序。而我进行的都是一模一样的操作。
 
create我只进行了一次,很多次的都是inifile.readstring()、Tmemo.loadfrom(a.txt)等操作,个别的ini文件(记录文件属性设定的ini文件)我create后,用好都马上free掉的。

忘了帖DoScript过程了:
procedure TWrits.DoScript(CurrentLine, SubS: Integer; Str, BName: String);
begin
Case StrCut.MValue.Lines.Count of
0:
if LowerCase(LeftStr(Str, 4)) = '<cm>' then
begin
MakeCM(RightStr(Str, Length(Str) - 4));//<cm>补处理
GetMFromMValue(CurrentLine, SubS, BName);
end
else
begin
Broad.M.Lines.Strings[CurrentLine] := '&nbsp';
Broad.DetailCount := Broad.DetailCount + 1;
end;
1:
begin
Broad.M.Lines.Strings[CurrentLine] := StrCut.MValue.Lines.Strings[0];
CountLine(Length(StrCut.MValue.Lines.Strings[0]), SubS, BName);
end;
else
GetMFromMValue(CurrentLine, SubS, BName);
end;
StrCut.MValue.Lines.Clear;
StrCut.EValue.Text := '';
StrCut.ECut.Text := '';
end;
 
你说的没错!确实没法看你的代码。
我觉得应当用好面向对象这个思想,用好接口这个技术,代码肯定会比你写的要精炼。不过,这个问题应当别论了。
 
我要睡觉了。明天再讨论吧!
 
我用Showmessage('执行到此处');进行测试,发现是在这一句:
S := Broad.IniSub.ReadString(SName, S, '');执行发生错误
不可思议。

Showmessage('执行到此处');放在它前面时,跳出了提示,反之弹出错误以后,后面不执行了,没有跳出'执行到此处'。

我回到前面的帖子里用
-------------------
错误的句子。
-------------------
做个标志。
Readstring的时候怎么会出错啊?

我都改成:
try S := Broad.IniSub.ReadString(SName, S, ''); except S := ''; end;
了,结果还是照样弹出个大大错误,一直以为ini.readstring很安全的呢。

现在我改成:
if Broad.IniSub.ValueExists(sname, s) then S := Broad.IniSub.ReadString(SName, S, '') else S := '';
照样出错else S := '';应该不会出错吧,那前面呢?Valueexists证明该值存在,可存在值为什么读成错误。
 
翻遍了大半个internet,好象普遍认为ini比较弱,容易出错,不幸的是我做的软件偏偏要委以重任,大部分功能都靠它实现,对于出错的部分,我现在把它读到ValueListEditor中,再进行读取,在读ValueListEditor过程中,由于仍发生错误:读到了00000000地址(太快了),再对它前后各加了一个Sleep(10),不管是不是只需要一个就行了,现在总算能用,只希望它不要再出错。
结帖
 
Broad.IniSub.ReadString(SName, S, '');

这句话里面的S会不会是空的,你跟踪一下,或者是值有问题,你检查一下。另外我的建议的是这样。

ini文件在开始的时候读取,然后你把这些值的内容存到一个对象里面,然后对这个对象进行操作,最后把这个对象的内容保存回ini文件,这样ini文件对象就不会长期在内存中了,你的代码改造成处理某个对方应该也比较方便
 
第一:减少 Tinifile 数量
第二:自己创建的对象用完之后要释放。
第三:用指针申请的内存用完之后也要释放。
 
后退
顶部