Pascal指针改变其指向值

时间:2014-11-14 08:44:12

标签: delphi pointers pascal

我对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; 

所有航路点都保存在一个数组中。 现在,当我试图读出路径的值时,我得到了神秘的结果:

writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[1].pathRefs[0]^.distance);

两者都应该打印相同的值,但它们不会。 然而,更神秘的是,即使我尝试以下内容:

writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);
writeln(waypoints[0].pathRefs[0]^.distance);

我得到2个不同的值。 (正确的 - 先是173 - 然后是2次。)

waypoints[0].pathRefs[0]^

总是指向同一个地址,因此我很困惑。我希望有人知道这个问题。

编辑: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;                      

1 个答案:

答案 0 :(得分:6)

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

  1. 如果你有getter方法支持的waypoints[0]pathRefs[0]的数组类型属性:那么这些方法可能会产生可能导致问题的副作用。 (显然,这不是的情况)。
  2. 如果您的指针正在引用"无效的内存"位置:然后由其他代码覆盖内存可能导致值发生变化。 (这是你的问题。)
  3. 您要添加的路径在堆栈中声明:

    var
      p: path;  //<-- Stack variable
    begin
      ...    
      addPath(@p);
    end; //<-- When you leave the method the stack variable is no longer valid.
    
    • 作为此代码的结果,您的wRef^.pathRefs[??]指向调用堆栈中较高的地址。
    • 当您调用其他方法时,相同的内存将用于其他目的。
    • 值可以改变。

    您需要确保指向堆上的内存。您可以使用动态内存分配例程执行此操作:NewDisposeGetMemFreeMem

    修改

    Documentation关于动态内存分配例程。

    如何更改代码的示例:

    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;
    

    注意:您必须弄清楚处理动态分配内存的位置和时间。


    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.