delphipointerspascal

Pascal Pointers changing their pointing value


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;                      

Solution

  • There are 2 things that could cause this kind of unexpected behaviour:

    1. If you have array-type properties for 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).
    2. If your pointers are referecing "invalid memory" locations: then memory overwrites by other code can cause the value to change. (And this is your problem.)

    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.
    

    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.