2014-11-14 64 views
1

我對帕斯卡相對陌生,目前正在使用指針。 我有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 - 之後的所有時間。)

waypoints[0].pathRefs[0]^ 

總是指向相同的地址,因此我很困惑。我希望有人知道這個問題。

編輯:2似乎是默認值,因爲它也返回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;      
+0

你能告訴你如何設置pathRefs數組嗎? –

+0

如果你還在使用數組,你爲什麼要使用指針? –

+1

請顯示一個完整的測試,以便您的問題可以複製。 @ No'amNewman,指針在這裏,以便記錄可以在不重複的情況下交叉引用數據。 –

回答

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. 
  • 作爲該代碼的結果是,你wRef^.pathRefs[??]指向一個在通話堆棧上位置較高。
  • 當你調用其他方法時,同樣的內存被用於其他目的。
  • 而且值可以改變。

您需要確保指向堆上的內存。您可以通過使用動態內存分配例程來執行此操作:New,Dispose,GetMem,FreeMem

編輯

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.