delphi Pascal指针更改其指向值

qlvxas9a  于 2023-02-15  发布在  其他
关注(0)|答案(2)|浏览(151)

我是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);

我得到两个不同的值。(正确的一个- 173 -第一次,然后2所有的时间之后。)

waypoints[0].pathRefs[0]^

总是指向同一个地址,所以我很困惑。我希望有人知道这个问题。
EDIT:2似乎是默认值,因为如果我在创建路径时没有保存任何值到“distance”,它也会返回2。
编辑2:这里的代码的航点和路径创建。我认为一定是有一个失败。我现在可能是混乱的设计,因为程序内的程序。我只是试验。

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;
qybjjes1

qybjjes11#

有两种情况可能导致这种意外行为:
1.如果waypoints[0]pathRefs[0]的数组类型属性由getter方法支持:那么这些方法就有可能产生副作用,从而导致问题的发生。(* 显然,这里不是这种情况 )。
1.如果指针指向"无效内存"位置:那么内存被其他代码覆盖可能会导致值更改。(
而这是你的问题 *。)
在堆栈上声明要添加的路径:

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

您需要确保指向堆上的内存。您可以使用动态内存分配例程来实现这一点:一米三氮一x一米四氮一x一米五氮一x一米六氮一x

    • 编辑**

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.
h7appiyu

h7appiyu2#

您正在将wayPointRefA和wayPointRefB传递给buildPath过程。该过程应该在这两个航点之间创建一条路径,然后将其添加到这两个航点的路径数组中。所有业务逻辑都很好。
问题就出在这里,你还没有考虑到生命周期,你的wayPointA和wayPointB将持有一个指向P的指针,而P存在于buildPath过程的堆栈帧中,这意味着在你真正创建了路径P(表示wayPointA和wayPointB之间的路径),并将指向P的指针添加到这两个waypoint的路径数组中之后,P超出了作用域,指向P的指针变成了一个悬空指针,我认为程序不会崩溃,因为当你调用另一个过程或函数时,你的程序再次拥有了P曾经所在的那部分内存,但现在那部分内存被用于完全不同的用途。P不再存储在那里,这就是为什么当你试图访问pathref的字段时会得到无意义的值。
不幸的是,Pascal编译器接受了这些垃圾代码。不幸的是,Pascal中没有拥有和借用内存的概念,也没有生命周期注解。在Rust @P中(或者更确切地说&P)意味着你借用了数据,你必须指定生存期注解,因为P没有足够长的生存期,你的代码将无法编译。你也可以让路点拥有路径数据,这样你的代码就不会编译,因为@P(Rust中的&P)不是一个被拥有的类型。
如果你在Rust中编写程序,你将把P Package 在一个引用计数器(Rc)中,因为你有2个拥有相同路径数据的航点。
在Pascal中,你可以使用new关键字(许多其他语言也使用这个关键字)把path放到堆中。这样,路点就不会有一个悬空指针。不过,要记住你是循环引用路点和路径的,所以你需要非常小心内存管理。如果可以的话,最好避免出现路径和路点循环引用的情况。

相关问题