Ant*_*ans 4 delphi pointers dispose
我有一些指向一些复杂记录的指针.有时当我尝试处理它们时,我得到无效的指针操作错误.我不确定我是否正确地创建和处理它们.记录看起来像这样:
type
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80; // that's array[0..80] of WideChar
pFullPath: PVeryLongPath; // this is pointer to array of WideChar
pNext: PFILEDATA; // this is pointer to the next TFILEDATA record
end;
Run Code Online (Sandbox Code Playgroud)
据我所知,当我想要一个指向这样的记录的指针时,我需要初始化指针和动态数组,如下所示:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
New(Result^.pFullPath);
end;
Run Code Online (Sandbox Code Playgroud)
现在处理这些记录的系列我写道:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData^.pNext <> nil do begin
pNextData := pData^.pNext; // Save pointer to the next record
Finalize(pData^.pFullPath); // Free dynamic array
Dispose(pData); // Free the record
pData := pNextData;
end;
Finalize(pData^.pFullPath);
Dispose(pData);
pData := nil;
end;
Run Code Online (Sandbox Code Playgroud)
当我在Delphi 2010 IDE中以调试模式(F9)运行我的程序时,会发生一些奇怪的事情.当我通过F8步进DisposeData代码时,似乎程序跳过Finalize(pData ^ .pFullPath)行并跳转到Dispose(pData).这是正常的吗?此外,当执行Dispose(pData)时,显示指针内容的"局部变量"窗口不会更改.这是否意味着处置失败?
编辑:PVeryLongPath是:
type
TVeryLongPath = array of WideChar;
PVeryLongPath = ^TVeryLongPath;
Run Code Online (Sandbox Code Playgroud)
EDIT2
所以我创建2个TFILEDATA记录然后我处理它们.然后我再次创建相同的2条记录.由于某种原因,第二次记录中的这个时间pNext不是零.它指向第一记录.处理这个奇怪的事情会导致无效的指针操作错误.我随机地在DisposeData过程中插入了pData ^ .pNext:= nil.现在代码看起来像这样:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
pData^.pNext := nil; // <----
Dispose(pData^.pFullPath);
Dispose(pData);
pData := pNextData;
end;
end;
Run Code Online (Sandbox Code Playgroud)
错误消失了.我将尝试将PVeryLongPath更改为TVeryLongPath.
首先,如果你释放了某些内容,指针的内容就不会改变.这就是为什么你没有看到局部变量显示的变化.
编辑:声明 pFullPath
为 TVeryLongPath
.这已经是一个引用类型了,你不应该使用指向这种类型的指针.在这种情况下,New()不会按照您的想法执行.
如果你将它声明为UnicodeString,或者如果你的Delphi没有那个,那么它可能会更好,WideString.
如果pFullPath被声明为动态"WideChar数组",那么就不应该使用New().对于动态数组,请使用SetLength()而不使用其他内容.Dispose()将正确处理您记录中的所有项目,所以只需:
New(Result);
SetLength(Result^.pFullPath, size_you_need);
Run Code Online (Sandbox Code Playgroud)
然后:
Dispose(pData);
Run Code Online (Sandbox Code Playgroud)
在普通代码中,您永远不必调用Finalize().只要将正确类型的指针传递给Dispose(),Dispose就会完成这一切.
您接受 Serg 的回答这一事实表明您的节点创建代码有问题。您对该答案的评论证实了这一点。
我将其添加为新答案,因为对问题的编辑显着改变了它。
链表代码应该如下所示:
var
Head: PNode=nil;
//this may be a global variable, or better, a field in a class,
//in which case it would be initialized to nil on creation
function AddNode(var Head: PNode): PNode;
begin
New(Result);
Result.Next := Head;
Head := Result;
end;
Run Code Online (Sandbox Code Playgroud)
请注意,我们正在将节点添加到列表的头部。我们不需要初始化Next
到nil
任何地方,因为我们总是将另一个节点指针分配给Next
。这条规则很重要。
我已将其编写为返回新节点的函数。由于新节点总是添加在头部,这有点多余。因为您可以忽略函数返回值,所以它实际上不会造成任何损害。
有时您可能希望在添加新节点时初始化节点的内容。例如:
function AddNode(var Head: PNode; const Caption: string): PNode;
begin
New(Result);
Result.Caption := Caption;
Result.Next := Head;
Head := Result;
end;
Run Code Online (Sandbox Code Playgroud)
我更喜欢这种方法。始终确保您的字段已初始化。如果零初始化适合您,那么您可以使用AllocMem
它来创建节点。
这是使用这种方法的更具体的示例:
type
PNode = ^TNode;
TNode = record
Caption: string;
Next: PNode;
end;
procedure PopulateList(Items: TStrings);
var
Item: string;
begin
for Item in Items do
AddNode(Head, Item);
end;
Run Code Online (Sandbox Code Playgroud)
要销毁列表,代码如下运行:
procedure DestroyList(var Head: PNode);
var
Next: PNode;
begin
while Assigned(Head) do begin
Next := Head.Next;
Dispose(Head);
Head := Next;
end;
end;
Run Code Online (Sandbox Code Playgroud)
可以清楚地看到,这个方法只有在Head
is时才能返回nil
。
如果将链表封装在类中,则可以使头指针成为该类的成员,从而避免传递它。
我想说的要点是手动内存分配代码很微妙。在细节上很容易犯小错误。在这种情况下,将精致的代码放入辅助函数或方法中是值得的,这样您只需编写一次。链表是喜欢用泛型解决的问题的一个很好的例子。您可以编写一次内存管理代码,然后将其重新用于各种不同的节点类型。
归档时间: |
|
查看次数: |
3398 次 |
最近记录: |