我有一些指向一些复杂记录的指针。有时当我尝试处理它们时,我得到无效的指针操作错误。我不确定我是否正确地创建和处理它们。 记录如下:
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;
当我在Delphi 2010 IDE中以调试模式(F9)运行我的程序时,会发生一些奇怪的事情。当我通过F8步进DisposeData代码时,似乎程序跳过Finalize(pData ^ .pFullPath)行并跳转到Dispose(pData)。这是正常的吗?此外,当执行Dispose(pData)时,显示指针内容的“局部变量”窗口不会更改。这是否意味着处置失败?
编辑: PVeryLongPath是:
type
TVeryLongPath = array of WideChar;
PVeryLongPath = ^TVeryLongPath;
EDIT2
所以我创建了两个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;
错误消失了。 我将尝试将PVeryLongPath更改为TVeryLongPath。
答案 0 :(得分:5)
首先,如果你释放了某些内容,指针的内容就不会改变。这就是为什么你没有看到局部变量显示的变化。
编辑:声明 pFullPath
为 TVeryLongPath
。 这是一种引用类型,您不应该使用指向这种类型的指针。在这种情况下,New()不会按照您的想法执行。
如果你把它声明为UnicodeString,或者如果你的Delphi没有那个,那么可能会更好,WideString。
如果pFullPath被声明为动态“WideChar数组”,那么就不应该使用New()。对于动态数组,请使用SetLength()而不使用其他内容。 Dispose()将正确处理记录中的所有项目,所以只需:
New(Result);
SetLength(Result^.pFullPath, size_you_need);
以后:
Dispose(pData);
在普通代码中,您永远不必调用Finalize()。只要您将正确类型的指针传递给Dispose(),Dispose就会完成这一切。
答案 1 :(得分:4)
我建议您避免使用WideChar的动态数组,这对使用来说并不方便。如果你有Delphi 2009或更高版本,则使用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;
答案 2 :(得分: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;
请注意,我们正在将节点添加到列表的头部。我们无需在任何地方将Next
初始化为nil
,因为我们始终将另一个节点指针指定给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
时返回。
如果将链接列表封装在类中,则可以将头指针作为类的成员,并避免传递它。
我想说的是手动内存分配代码很精细。在细节上很容易犯错误。在这样的情况下,将精巧的代码放在辅助函数或方法中是值得的,因此您只需要编写一次。链接列表是一个很好的例子,可以解决泛型问题。您可以编写一次内存管理代码,并将其重用于各种不同的节点类型。
答案 3 :(得分:1)
您还应将pNext
字段初始化为nil
- 如果没有它,您最终会获得访问权限违规。考虑到之前答案中已经说过的内容,您可以将代码更改为
type
TFileType80 = array[0..80] of WideChar;
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80;
FullPath: WideString;
pNext: PFILEDATA;
end;
function GimmeNewData: PFILEDATA;
begin
New(Result);
Result^.pNext:= nil;
end;
答案 4 :(得分:1)
我认为你的大多数问题都是由New()
给你的内存归零的假设引起的。我很确定(如果我错的话,我也肯定有人会纠正我),但德尔福并不能保证是这样的。这可以通过将代码更改为:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
ZeroMemory(Result, SizeOf(TFILEDATA));
end;
您应该始终将为记录分配的内存归零,或者至少用其他相关内容填充所有字段。此行为与对象不同,对象保证在分配时归零。
希望这有帮助。