I'm relative new to Pascal and currently working with pointers. I've got 2 records, one of them contains 2 pointers to the other record type.
type
WaypointRef = ^Waypoint;
PathRef = ^Path;
Waypoint = record
id: integer;
Name: string;
pathRefs: array of PathRef;
end;
Path = record
distance: integer;
WaypointRefA, WaypointRefB: WaypointRef;
end;
All waypoints are saved in an array. Now, when I try to read out the value of a path I get mysterious results:
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[1].pathRefs[0]^.distance);
Both should print the same values but they don't. However, the more mysterious thing is that even if I try the following:
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);
I get 2 different values. (The right one - 173 - first and then 2 all times afterwards.)
waypoints[0].pathRefs[0]^
always points to the same address and thus I'm very confused. I hope someone knows the issue.
EDIT: 2 seems to be the default value as it also returns 2 if I don't save any value to "distance" at the path creation.
EDIT2: Here the code of the waypoint and path-creation. I think there must be an failure. I now it might be confusing design because of the procedures inside the procedures. I'm just experimenting.
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;
There are 2 things that could cause this kind of unexpected behaviour:
waypoints[0]
and pathRefs[0]
backed by getter methods: then there could be the possibility of those methods having side-effects which would cause the problem. (Obviously that's not the case here).The path that you're adding is declared on the stack:
var
p: path; //<-- Stack variable
begin
...
addPath(@p);
end; //<-- When you leave the method the stack variable is no longer valid.
wRef^.pathRefs[??]
points to an address higher up on the call stack. You need to ensure that you point to memory on the heap. You do this by using dynamic memory allocation routines: New
, Dispose
, GetMem
, FreeMem
.
EDIT
Documentation about dynamic memory allocation routines.
Example of how you could change your code:
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;
NOTE: You'll have to figure out where and when to dispose of the dynamically allocated memory.
EDIT2 Add a simple console app to demonstrate the problem.
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.