End*_*eit 1 delphi pascal pointers
我是Pascal的新手,目前正在使用指针.我有2条记录,其中一条包含指向另一条记录类型的2条指针.
type
WaypointRef = ^Waypoint;
PathRef = ^Path;
Waypoint = record
id: integer;
Name: string;
pathRefs: array of PathRef;
end;
Path = record
distance: integer;
WaypointRefA, WaypointRefB: WaypointRef;
end;
Run Code Online (Sandbox Code Playgroud)
所有航路点都保存在一个阵列中.现在,当我试图读出路径的值时,我得到了神秘的结果:
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[1].pathRefs[0]^.distance);
Run Code Online (Sandbox Code Playgroud)
两者都应该打印相同的值,但它们不会.然而,更神秘的是,即使我尝试以下内容:
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);
Run Code Online (Sandbox Code Playgroud)
我得到2个不同的值.(正确的 - 先是173 - 然后是2次.)
waypoints[0].pathRefs[0]^
Run Code Online (Sandbox Code Playgroud)
总是指向同一个地址,因此我很困惑.我希望有人知道这个问题.
编辑:2似乎是默认值,因为如果我在路径创建时没有将任何值保存到"距离",它也会返回2.
EDIT2:这里是航点和路径创建的代码.我认为一定有失败.我现在可能会因设备内部的程序而混淆设计.我只是在试验.
procedure buildWaypoint(Name: string);
procedure addWaypoint(w: Waypoint);
var
lngth: integer;
begin
lngth := Length(waypoints);
SetLength(waypoints, lngth + 1);
waypoints[lngth] := w;
end;
var
w: Waypoint;
begin
w.id := id;
id := id + 1;
w.Name := Name;
addWaypoint(w);
end;
procedure buildPath(waypointRefA, waypointRefB: WaypointRef; distance: integer);
procedure addPath(pRef: PathRef);
procedure addPathToWaypoint(pRef: PathRef; wRef: WaypointRef);
var
lngth: integer;
begin
lngth := length(wRef^.pathRefs);
SetLength(wRef^.pathRefs, lngth + 1);
wRef^.pathRefs[lngth] := pRef;
end;
begin
addPathToWaypoint(pRef, pRef^.WaypointRefA);
addPathToWaypoint(pRef, pRef^.WaypointRefB);
end;
var
p: path;
begin
p.distance := distance;
p.WaypointRefA := waypointRefA;
p.WaypointRefB := waypointRefB;
addPath(@p);
end;
Run Code Online (Sandbox Code Playgroud)
有两件事可能导致这种意外行为:
waypoints[0],并pathRefs[0]通过getter方法支持:那么有可能是具有副作用的,这将导致该问题的这些方法的可能性.(显然情况并非如此).您要添加的路径在堆栈中声明:
var
p: path; //<-- Stack variable
begin
...
addPath(@p);
end; //<-- When you leave the method the stack variable is no longer valid.
Run Code Online (Sandbox Code Playgroud)
wRef^.pathRefs[??]指向调用堆栈上更高的地址. 您需要确保指向堆上的内存.通过使用动态内存分配程序做到这一点:New,Dispose,GetMem,FreeMem.
编辑
有关动态内存分配例程的文档.
如何更改代码的示例:
procedure addPathToWaypoint(pRef: PathRef; wRef: WaypointRef);
var
lngth: integer;
LpRefOnHeap: PathRef;
begin
lngth := length(wRef^.pathRefs);
SetLength(wRef^.pathRefs, lngth + 1);
New(LpRefOnHeap); //Allocate heap memory
LpRefOnHeap^ := pRef^; //Copy data pointed to by pRef to heap
wRef^.pathRefs[lngth] := LpRefOnHeap; //Hold reference to an address that won't
//become invalid when stack unwinds.
end;
Run Code Online (Sandbox Code Playgroud)
注意:您必须弄清楚处理动态分配的内存的位置和时间.
EDIT2 添加一个简单的控制台应用程序来演示此问题.
program InvalidUseOfStackVar;
{$APPTYPE CONSOLE}
type
PData = ^TData;
TData = record
Value: Integer;
end;
var
GData: PData;
procedure SetData;
var
LData: TData; //Stack variable will no longer be valid when routine exits.
begin
LData.Value := 42; //The initial value pointed to by GData
GData := @LData; //The global var will continue to point to invalid memory after exit.
end;
procedure ChangeStack;
var
//This is written to have the same stack layout as the previous routine.
LData: TData;
begin
LData.Value := 777; //This unintentionally changes data pointed to by the global var
end;
begin
SetData; //Sets GData, but GData points to an address on the call stack
Writeln(GData^.Value); //Writes 42 because that's what was on the stack at the time of the method call.
ChangeStack; //Updates the stack variable to a different value
Writeln(GData^.Value); //Now writes 777 because GData points to the same location in memory, but the
//data at that location was changed.
Writeln(GData^.Value); //Note: calling the Writeln method above also changes the stack.
//The only difference is that it is less predictable for us to determine
//how the stack will be changed.
Readln;
end.
Run Code Online (Sandbox Code Playgroud)