Search code examples
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.
    
    • As a result of this code, your wRef^.pathRefs[??] points to an address higher up on the call stack.
    • As you call other methods, that same memory gets used for other purposes.
    • And the values can change.

    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.