处理指向复杂记录的指针

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.

Rud*_*uis 5

首先,如果你释放了某些内容,指针的内容就不会改变.这就是为什么你没有看到局部变量显示的变化.

编辑:声明 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就会完成这一切.

FWIW,我会推荐这篇和我的这篇文章.


Dav*_*nan 4

您接受 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)

请注意,我们正在将节点添加到列表的头部。我们不需要初始化Nextnil任何地方,因为我们总是将另一个节点指针分配给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)

可以清楚地看到,这个方法只有在Headis时才能返回nil

如果将链表封装在类中,则可以使头指针成为该类的成员,从而避免传递它。

我想说的要点是手动内存分配代码很微妙。在细节上很容易犯小错误。在这种情况下,将精致的代码放入辅助函数或方法中是值得的,这样您只需编写一次。链表是喜欢用泛型解决的问题的一个很好的例子。您可以编写一次内存管理代码,然后将其重新用于各种不同的节点类型。