我有一些指向一些复杂记录的指针列表。有时,当我尝试处理它们时,我得到无效的指针操作错误。我真的不确定我是否正确地创建和处理了它们。记录如下所示:
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;据我所知,当我想要一个指向这样的记录的指针时,我需要初始化指针和动态数组,如下所示:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
New(Result^.pFullPath);
end;现在,为了处理一系列这些记录,我写了以下内容:
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;当我在Delphi2010IDE中以调试模式(F9)运行我的程序时,发生了一些奇怪的事情。当我使用F8单步执行DisposeData代码时,程序似乎跳过了Finalize(pData^.pFullPath)行,而跳到了Dispose(pData)。这是正常的吗?此外,在执行Dispose(pData)时,显示指针内容的“局部变量”窗口也不会更改。这是否意味着dispose失败了?
编辑: PVeryLongPath为:
type
TVeryLongPath = array of WideChar;
PVeryLongPath = ^TVeryLongPath;Edit2
因此,我创建了两个TFILEDATA记录,然后将它们处理掉。然后,我再次创建相同的两个记录。由于某些原因,这一次第二条记录中的pNext不是nil。它指向第一个记录。处理这个奇怪的东西会得到无效的指针操作错误。我随机地在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;错误消失了。我将尝试将PVeryLongPath更改为TVeryLongPath。
发布于 2011-08-06 17:25:35
您接受了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;请注意,我们正在将节点添加到列表的头部。我们不需要将Next初始化为nil anywhere,因为我们总是将另一个节点指针分配给Next。这条规则很重要。
我把它写成一个返回新节点的函数。因为新节点总是添加在头部,所以这有点多余。因为你可以忽略函数的返回值,所以这并没有什么坏处。
有时,您可能希望在添加新节点时初始化节点的内容。例如:
function AddNode(var Head: PNode; const Caption: string): PNode;
begin
New(Result);
Result.Caption := Caption;
Result.Next := Head;
Head := Result;
end;我更喜欢这种方法。始终确保您的字段已初始化。如果零初始化对您来说没问题,那么您可以使用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;要销毁列表,代码运行如下:
procedure DestroyList(var Head: PNode);
var
Next: PNode;
begin
while Assigned(Head) do begin
Next := Head.Next;
Dispose(Head);
Head := Next;
end;
end;您可以清楚地看到,只有当Head为nil时,此方法才能返回。
如果您将链表封装在一个类中,则可以使头指针成为该类的成员,从而避免了传递它的需要。
我想说的主要一点是,手动内存分配代码是微妙的。很容易在细节上犯一些小错误。在这种情况下,将微妙的代码放在helper函数或方法中是值得的,因此您只需要编写一次。链表是一个很好的例子,它喜欢用泛型来解决问题。您可以只编写一次内存管理代码,然后将其重用于各种不同的节点类型。
发布于 2011-08-06 05:33:16
首先,如果你释放了一些东西,指向它的指针的内容不会改变。这就是为什么在局部变量显示中看不到变化的原因。
编辑:将pFullPath声明为TVeryLongPath。这已经是一个引用类型,您不应该使用指向此类类型的指针。在这种情况下,New()并不像您认为的那样工作。
如果你把它声明为UnicodeString可能会更好,或者如果你的Delphi没有,WideString。
如果pFullPath被声明为一个动态的“WideChar数组”,那么您不应该对它使用New()。对于动态数组,使用SetLength()而不使用其他方法。Dispose()将正确地处理记录中的所有项,所以只需这样做:
New(Result);
SetLength(Result^.pFullPath, size_you_need);之后的版本:
Dispose(pData);在普通代码中,您永远不需要调用Finalize()。这一切都由Dispose负责,只要您将正确类型的指针传递给Dispose()即可。
顺便说一句,我推荐我的this和this文章。
发布于 2011-08-06 05:26:36
我建议您避免使用WideChar的动态数组,因为使用起来一点也不方便。如果您使用的是Delphi2009或更高版本,请使用string,或者对于较早的Delphi版本,请使用WideString。这两种类型都是带有WideChar元素的动态字符串类型。你可以赋值给它们,Delphi会处理所有的分配。
因此,假设您现在拥有以下记录:
TFILEDATA = record
Description80: TFileType80;
pFullPath: WideString;
pNext: PFILEDATA;
end;你可以让事情变得相当简单。
function GimmeNewData(): PFILEDATA;
begin
New(Result);
end;
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
Dispose(pData);
pData := pNextData;
end;
end;https://stackoverflow.com/questions/6962735
复制相似问题