Pascal指针改变其指向值

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)

Dis*_*ned 6

有两件事可能导致这种意外行为:

  1. 如果您有数组类型的属性waypoints[0],并pathRefs[0]通过getter方法支持:那么有可能是具有副作用的,这将导致该问题的这些方法的可能性.(显然情况并非如此).
  2. 如果你的指针正在引用"无效内存"位置:那么其他代码覆盖内存会导致值发生变化.(这是你的问题.)

您要添加的路径在堆栈中声明:

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)