2

我在 Windows Vista 笔记本电脑上使用Free Pascal编译器 2.6.0 版在 Pascal 中编写了图灵机。在编译和测试结果后,我使用了“heaptrc”单元来检测一些内存泄漏。不幸的是,该程序找到了几个我无法修复的问题。

我已经通过 Google 和 Stack Overflow 寻找解决方案。在那里,我发现了我在程序中使用的“try finally”之类的结构。我将所有动态数组的大小重置为零以确保它们已被清理。这些措施解决了一些内存泄漏问题,但仍有八个未释放的内存块。

然后我在一个德国德尔福论坛上寻求帮助,在那里我得到了一些帮助,遗憾的是没有帮助。对于那些懂德语的人来说,它在Free Pascal 2.6.0 中。图灵机内存泄漏

该程序的基本工作方式是通过读取 .txt 文件来创建和填充指令表。然后要求用户提供磁带的初始数据。在以下循环中,根据指令表更改数据,直到机器停止。

然后应该清理所有内容,但这似乎无法正常工作。如果我在调试器中运行程序,程序以退出代码“01”结束,根据文档,这意味着“无效的函数号尝试了无效的操作系统调用。”。但这也没有多大帮助。

如果我正确理解了“try finally”结构,则无论发生什么都应该调用并执行“Machine.Free”,因此应该正确清理所有内容。我通过反复试验学习了很多编程,所以我真的很想知道,为什么事情不能正常工作,而不仅仅是一个解决方案。

当然,如果有一些严重的设计缺陷,我愿意更改我的代码。这些是源代码文件。'heaptrc' 的输出在 'memory.txt' 中:

图灵帕斯

{turing.pas}
{Program to imitate a Turing machine, based on the principles by Alan Turing.}

program Turing;

{$mode objfpc}{$H+}

uses
  sysutils,  {Used for the conversion from Integer to String.}
  TuringHead, {Unit for the head instructions.}
  TuringTable; {Unit for the instruction table.}

type
{Declarations of self made types}
  TField = Array of Char;

{Class declarations}
  TMachine = class(TObject)
    private
      Tape: TField; {The tape, from which data is read or on which data is written.}
      TapeSize: Integer; {The length of the tape at the start of the machine.}
      Head: THead; {The head, which reads, writes and moves. Look in "turinghead.pas" to see, how it works.}
      InstructionTable: TInstructionTable; {The table, which contains the instructions for the machine. Look in "turingtable.pas" to see, how it works.}
      ConstantOutput: Boolean; {If its value is "True", there will be constant output.
                                It is adjustable for performance, because the machine is much slower when it has to output data all the time.}
      procedure GetSettings(); {Ask the user for different settings.}
      procedure GetInput(); {Read the input from the user.}
      procedure TapeResize(OldSize: Integer; Direction: Char); {Expand the tape and initialize a new element.}
      procedure TapeCopy(); {Copies the elements of the array to the right.}
      procedure Display(State: Char; ReadData: Char; WriteData: Char; MoveInstruction: Char; HeadPosition: Integer); {Display the machines current status.}
    public
      constructor Create(); {Prepare the machine.}
      procedure Run(); {Run the machine.}
      destructor Destroy(); Override;{Free all objects, the machine uses.}
    protected
    published
  end;

var
  Machine: TMachine;

procedure TMachine.GetSettings();
var
  OutputType: Char;
begin
  WriteLn('If you want constant output, please type "y", if not, please type "n"!');
  ReadLn(OutputType);
  case OutputType of
    'n': ConstantOutput := False;
    'y': ConstantOutput := True
  end;
  WriteLn('Please input the start tape length! It will expand automatically, if it overflows.');
  ReadLn(TapeSize);
  if TapeSize > 0 then {Test, if the input makes sense, to prevent errors.}
    SetLength(Tape, TapeSize)
  else
    begin
      WriteLn('Please input a length greater than zero!');
      GetSettings()
    end
end;

procedure TMachine.GetInput();
var
  UserInput: String;
  Data: Char;
  HeadPosition: Integer;
begin
  WriteLn('Please input the data for the tape!');
  SetLength(UserInput, TapeSize);
  ReadLn(UserInput);
  if UserInput[TapeSize] <> '' then
    begin
      HeadPosition := 0;
      while HeadPosition < TapeSize do
        begin
          Data := UserInput[HeadPosition + 1]; {The data is stored one place ahead of the current head position.}
          Head.WriteData(Tape, HeadPosition, Data);
          HeadPosition := Head.Move(HeadPosition, 'R')
        end;
      WriteLn('Thank you, these are the steps of the machine:')
    end
  else
    begin
      WriteLn('Please fill the whole tape with data!');
      GetInput()
    end
end;  

procedure TMachine.TapeResize(OldSize: Integer; Direction: Char);
var
  NewSize: Integer;
begin
  case Direction of
    'L': begin
      NewSize := OldSize + 1;
      SetLength(Tape, NewSize);
      TapeCopy(); {Copy the elements of the array, to make space for the new element.}
      Head.WriteData(Tape, Low(Tape), '0') {Initialize the new tape element with the empty data.}
    end;
    'R': begin
      NewSize := OldSize + 1;
      SetLength(Tape, NewSize);
      Head.WriteData(Tape, High(Tape), '0') {Initialize the new tape element with the empty data.}
    end
  end
end;

procedure TMachine.TapeCopy();
var
  Counter: Integer;
begin
  Counter := High(Tape);
  while Counter > 0 do
    begin
      Tape[Counter] := Tape[Counter - 1];
      Dec(Counter, 1)
    end
end;

procedure TMachine.Display(State: Char; ReadData: Char; WriteData: Char; MoveInstruction: Char; HeadPosition: Integer);
var
  DispHead: Integer;
begin
  DispHead := 0;
  while DispHead < Length(Tape) do {Write the data on the tape to the output.}
    begin
      Write(Tape[DispHead]);
      DispHead := Head.Move(DispHead, 'R');
    end;
  Write(' State: ' + State + ' Read: ' + ReadData + ' Write: ' + WriteData +
        ' Move: ' + MoveInstruction  + ' Head: ' + IntToStr(HeadPosition + 1)); {Constructed string to write information about the machine.}
  WriteLn('')
end;  

constructor TMachine.Create();
begin
  inherited;
  Head := THead.Create();
  InstructionTable := TInstructionTable.Create();
  GetSettings();
  GetInput()
end; {TMachine.Initialize}

procedure TMachine.Run();
var
  TapeData: Char; 
  WriteData: Char;
  StateRegister: Char; 
  MoveInstruction: Char; 
  HeadPosition: Integer; 
  Running: Boolean; 
begin
  if TapeSize > 1 then
    HeadPosition := (Length(Tape) div 2) - 1 {The head starts in the middle of the tape.}
  else
    HeadPosition := 0;
  StateRegister := 'A'; {This is the start register.}
  Running := True;

  while Running do
    begin
      {Get instructions for the machine.}
      TapeData := Head.ReadData(Tape, HeadPosition);
      WriteData := InstructionTable.GetData(StateRegister, TapeData, 'W');
      MoveInstruction := InstructionTable.GetData(StateRegister, TapeData, 'M');

      if ConstantOutput then
        Display(StateRegister, TapeData, WriteData, MoveInstruction, HeadPosition);

      Head.WriteData(Tape, HeadPosition, WriteData);

      case MoveInstruction of {Depending on the instructions, move the head.}
        'S': HeadPosition := Head.Move(HeadPosition, 'S');
        'L': HeadPosition := Head.Move(HeadPosition, 'L');
        'R': HeadPosition := Head.Move(HeadPosition, 'R')
      end;

      if HeadPosition > High(Tape) then
        TapeResize(Length(Tape), 'R');

      if HeadPosition < Low(Tape) then {If the head is farther to the left, than the tape is long, create a new field on the tape.}
        begin
          TapeResize(Length(Tape), 'L'); 
          HeadPosition := 0
        end;

      {Get the next state of the machine.}
      StateRegister := InstructionTable.GetData(StateRegister, TapeData, 'N');

      if StateRegister = 'H' then {This is the halting register.}
        begin
          Display(StateRegister, TapeData, WriteData, MoveInstruction, HeadPosition);
          Running := Head.Halt()
        end
    end
end; {TMachine.Run}

destructor TMachine.Destroy();
begin
  Head.Free;
  InstructionTable.Free;
  SetLength(Tape, 0);
  WriteLn('The turing machine stopped. You can end the program by pressing enter.');
  inherited
end; {TMachine.Stop}


{Implementation of the main program.}
begin
  Machine := TMachine.Create();
  try
    Machine.Run()
  finally
    Machine.Free
  end;
  ReadLn()
end. {Turing}

图灵头.pas

{turinghead.pas}
{Unit for the head of the turing machine.}

unit TuringHead;

{$mode objfpc}{$H+}

interface

type
  THead = class(TObject)
    private
      function Stay(HeadPos: Integer): Integer; {Head does not move.}
      function MoveLeft(HeadPos: Integer): Integer; {Head moves leftwards.}
      function MoveRight(HeadPos: Integer): Integer; {Head moves rightwards.}
    public
      function Move(HeadPos: Integer; Direction: Char): Integer; {Public function, which calls 'Stay' or 'MoveLeft/Right'.}
      function ReadData(Tape: Array of Char; HeadPos: Integer): Char; {Reads data from the tape.}
      procedure WriteData(var Tape: Array of Char; HeadPos: Integer; Data: Char); {Writes data onto the tape.}
      function Halt(): Boolean; {Commands the head to stop moving.}
    protected
    published
  end;

implementation

function THead.Move(HeadPos: Integer; Direction: Char): Integer;
var
  NextPos: Integer;
begin
  case Direction of {Used this way, so only one function has to be public, not three.}
    'S': NextPos := Stay(HeadPos);
    'L': NextPos := MoveLeft(HeadPos);
    'R': NextPos := MoveRight(HeadPos)
  end;
  Move := NextPos
end; {THead.Move}

function THead.ReadData(Tape: Array of Char; HeadPos: Integer): Char;
var
  Data: Char;
begin
  Data := Tape[HeadPos];
  ReadData := Data
end; {THead.ReadData}

procedure THead.WriteData(var Tape: Array of Char; HeadPos: Integer; Data: Char);
begin
  Tape[HeadPos] := Data
end; {THead.WriteData}

function THead.Stay(HeadPos: Integer): Integer;
var
  NextPosition: Integer;
begin
  NextPosition := HeadPos;
  Stay := NextPosition
end; {THead.Stay}

function THead.MoveLeft(HeadPos: Integer): Integer;
var
  NextPosition: Integer;
begin
  NextPosition := HeadPos - 1;
  MoveLeft := NextPosition
end; {THead.MovetLeft}

function THead.MoveRight(HeadPos: Integer): Integer;
var
  NextPosition: Integer;
begin
  NextPosition := HeadPos + 1;
  MoveRight := NextPosition
end; {THead.MoveRight}

function THead.Halt(): Boolean;
begin
  Halt := False
end; {THead.Halt}

begin
end.

图灵表.pas

{turingtable.pas}
{Unit for creating and accessing the instruction table.}

unit TuringTable;

{$mode objfpc}{$H+}

interface

const
  TupelLength = 5; {The amount of characters, each tupel has.}

type
{Declarations of self made types}
  TTextFile = TextFile;
  TDataString = Array of String[TupelLength]; {Every tupel has its own data string.}
  TDataTable = record {The type of the record, which is used to look up the instructions for the machine.}
      State: Array of Char; {The current state of the machine.}
      Read:  Array of Char; {The read data.}
      Write: Array of Char; {The data, which has to be written onto the tape.}
      Move:  Array of Char; {The movement instruction for the head.}
      Next:  Array of Char  {The next state of the machine.}
    end;

{Class declarations}
  TInstructionTable = class(TObject)
    private
      TupelNumber: Word; {The number of seperate tupels, which are defined in the text file.}
      DataString: TDataString; {The strings, that have all the tupels.}
      DataTable: TDataTable;
      procedure FileRead();
      procedure ArrayResize(Size: Word); {Resizes all arrays, so they are only as big, as they have to be.}
      procedure TableFill(); {Fills the data table with data from the data string.}
      function GetWrite(CurrentState: Char; ReadData: Char): Char; {Functions, which return the wanted instruction from the table.}
      function GetMove(CurrentState: Char; ReadData: Char): Char;
      function GetNext(CurrentState: Char; ReadData: Char): Char;
    public
      constructor Create(); {Creates the data table, so it can be used.}
      function GetData(CurrentState: Char; ReadData: Char; DataType: Char): Char; {Public function to get instructions.}
      destructor Destroy(); Override;
    protected
    published
  end;

implementation

procedure TInstructionTable.FileRead();
const
  FileName = 'turingtable.txt'; {The text file, that contains the instructions.}
var
  Text: String[TupelLength]; {The read text, which is just one unorganised string.}
  CurrentTupel: Word; {Keeps track of the tupels.}
  DataFile: TTextFile;
begin
  SetLength(DataString, 256); {Make the array pretty big, so it gives enough space.}
  CurrentTupel := 0;
  Assign(DataFile, FileName); {Open the file.}
  Reset(DataFile);
  while not eof(DataFile) do {As long, as the procedure did not reach the end of the text file, it shall proceed.}
    begin
      ReadLn(DataFile, Text);
      if Text[1] <> '/' then {If the line starts with an '/', it is a comment and thus not necessary for the program.}
        begin
          DataString[CurrentTupel] := Text; {Fill the data string.}
          inc(CurrentTupel, 1)
        end
    end;
  ArrayResize(CurrentTupel);
  TupelNumber := CurrentTupel; {This is the maximum amount of tupels.}
  Close(DataFile)
end; {TinstructionTable.FileRead}

procedure TInstructionTable.ArrayResize(Size: Word);
begin
  SetLength(DataString, Size);
  SetLength(DataTable.State, Size);
  SetLength(DataTable.Read, Size);
  SetLength(DataTable.Write, Size);
  SetLength(DataTable.Move, Size);
  SetLength(DataTable.Next, Size)
end; {TInstructionTable.ArrayResize}

procedure TInstructionTable.TableFill();
var
  Position: Word;
  CurrentTupel: Word;
begin
  Position := 1;
  CurrentTupel := 0;
  while CurrentTupel <= TupelNumber do {Fill the record for each tupel.}
    begin
      while Position <= TupelLength do {Each tupel has a certain instruction at the same place, so the record is filled in a certain way.}
        begin
          case Position of
            1: DataTable.State[CurrentTupel] := DataString[CurrentTupel][Position];
            2: DataTable.Read[CurrentTupel]  := DataString[CurrentTupel][Position];
            3: DataTable.Write[CurrentTupel] := DataString[CurrentTupel][Position];
            4: DataTable.Move[CurrentTupel]  := DataString[CurrentTupel][Position];
            5: DataTable.Next[CurrentTupel]  := DataString[CurrentTupel][Position]
          end;
          inc(Position, 1)
        end;
        Position := 1;
        inc(CurrentTupel, 1)
    end
end; {TInstructionTable.TableFill}

function TInstructionTable.GetWrite(CurrentState: Char; ReadData: Char): Char;
var
  Write: Char;
  EntryFound: Boolean;
  CurrentTupel: Integer;
begin
  EntryFound := false;
  CurrentTupel := 0;
  while not EntryFound do
    if (DataTable.State[CurrentTupel] = CurrentState) and (DataTable.Read[CurrentTupel] = ReadData) then {Tests, if the data pair exists in the record.}
      EntryFound := True
    else
      inc(CurrentTupel, 1);
  Write := DataTable.Write[CurrentTupel];
  GetWrite := Write
end; {TInstructionTable.GetWrite}

function TInstructionTable.GetMove(CurrentState: Char; ReadData: Char): Char;
var
  Move: Char;
  EntryFound: Boolean;
  CurrentTupel: Integer;
begin
  EntryFound := false;
  CurrentTupel := 0;
  while not EntryFound do
    if (DataTable.State[CurrentTupel] = CurrentState) and (DataTable.Read[CurrentTupel] = ReadData) then {Tests, if the data pair exists in the record.}
      EntryFound := True
    else
      inc(CurrentTupel, 1);
  Move := DataTable.Move[CurrentTupel];
  GetMove := Move
end; {TInstructionTable.GetMove}

function TInstructionTable.GetNext(CurrentState: Char; ReadData: Char): Char;
var
  Next: Char;
  EntryFound: Boolean;
  CurrentTupel: Integer;
begin
  EntryFound := false;
  CurrentTupel := 0;
  while not EntryFound do
    if (DataTable.State[CurrentTupel] = CurrentState) and (DataTable.Read[CurrentTupel] = ReadData) then {Tests, if the data pair exists in the record.}
      EntryFound := True
    else
      inc(CurrentTupel, 1);
  Next := DataTable.Next[CurrentTupel];
  GetNext := Next
end; {TInstructionTable.GetNext}

constructor TInstructionTable.Create();
begin
  inherited;
  FileRead();
  TableFill()
end; {TInstructionTable.Initialize}

function TInstructionTable.GetData(CurrentState: Char; ReadData: Char; DataType: Char): Char;
var
  Data: Char;
begin
  case DataType of {Used this way, so only one public function exists, instead of three.}
    'W': Data := GetWrite(CurrentState, ReadData);
    'M': Data := GetMove(CurrentState, ReadData);
    'N': Data := GetNext(CurrentState, ReadData)
  end;
  GetData := Data
end; {TInstructionTable.GetData}

destructor TInstructionTable.Destroy();
begin
  ArrayResize(0);
  inherited
end;

begin
end. {TuringTable}

图灵表.txt

/This is the table for the turing machine.
/Here you can define the instructions for the machine.
/Please use the given format.
/The start state is 'A'. 
/Use 'S' for staying, 'L' for moving the head leftwards and 'R' for moving the head rightwards.
/'H' is used to stop the machine.
/The head starts in the middle of the tape.
/If the array is expanded, it is filled with '0'.
/Lines are commented out when they begin with '/'.
/State Read Write Move Next

/Busy beavers taken from en.wikipedia.org

/2-state, 2-symbol busy beaver
/A01LB
/A11RB
/B01RA
/B11LH

/3-state, 2-symbol busy beaver
/A01LB
/A11RC
/B01RA
/B11LB
/C01RB
/C11SH

/4-state, 2-symbol busy beaver
A01LB
A11RB
B01RA
B10RC
C01LH
C11RD
D01LD
D10LA

/5-state, 2-symbol best contender busy beaver
/A01LB
/A11RC
/B01LC
/B11LB
/C01LD
/C10RE
/D01RA
/D11RD
/E01LH
/E10RA

/6-state, 2-symbol best contender busy beaver
/A01LB
/A11RE
/B01LC
/B11LF
/C01RD
/C10LB
/D01LE
/D10RC
/E01RA
/E10LD
/F01RH
/F11LC

内存.txt

C:\Programming_Software\FreePascal\2.6.0\projects\Turing_Machine\memory\turing.exe 
Marked memory at $000A5200 invalid
Wrong signature $D4DF2FA1 instead of A76E4766
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Heap dump by heaptrc unit
714 memory blocks allocated : 14207/18256
706 memory blocks freed     : 14061/18080
8 unfreed memory blocks : 146
True heap size : 458752 (144 used in System startup)
True free heap : 457824
Should be : 457920
Call trace for block $000A53E0 size 22
  $004018CF  TMACHINE__TAPERESIZE,  line 104 of turing.pas
  $00401E81  TMACHINE__RUN,  line 181 of turing.pas
  $0040201D  main,  line 216 of turing.pas
  $0040C7B1
Marked memory at $000A5380 invalid
Wrong signature $B3102445 instead of 3D0C752B
  $004106C7
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Marked memory at $000A5320 invalid
Wrong signature $FECB68AA instead of D626F67E
  $004106C7
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Marked memory at $000A52C0 invalid
Wrong signature $E738AA53 instead of AFAF3597
  $004106C7
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Marked memory at $000A5260 invalid
Wrong signature $CD2CED58 instead of FC317DEE
  $004106C7
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Marked memory at $000A5200 invalid
Wrong signature $D4DF2FA1 instead of A76E4766
  $004106C7
  $0040F85B
  $0040F917
  $0041550D  TINSTRUCTIONTABLE__ARRAYRESIZE,  line 76 of turingtable.pas
  $004159FD  TINSTRUCTIONTABLE__DESTROY,  line 180 of turingtable.pas
  $00407162
  $00407162
  $0040C7B1
Call trace for block $000AC3C8 size 32
  $00401C59  TMACHINE__CREATE,  line 141 of turing.pas
  $00401FF4  main,  line 214 of turing.pas
  $0040C7B1
  $00610068
  $00650072
  $005C0064
  $00690057
  $0064006E
Call trace for block $000A51A0 size 24
  $00401FF4
  $0040C7B1
  $0040C7B1
4

1 回答 1

2

正如评论中所说,您的第一个问题是“无效内存”。只有当程序正常工作时,内存安全才会出现。查看各种检查(范围/溢出)。

我使用范围检查 (-Cr) 快速编译并得到以下输出:

An unhandled exception occurred at $00418609:
ERangeError: Range check error
   $00418609  TINSTRUCTIONTABLE__TABLEFILL,  line 95 of turingtable.pas
   $00418B56  TINSTRUCTIONTABLE__CREATE,  line 163 of turingtable.pas
   $00401CFA  TMACHINE__CREATE,  line 140 of turing.pas
   $004020AD  main,  line 211 of turing.pas

我的猜测是,当 fileread 初始化数据字符串时,您不会初始化数据表,它还包含几个动态数组。

如果一切都失败了,您可以使用Valgrind,但对于这种规模和复杂性的程序,这可能是矫枉过正。

从这个问题中吸取的教训:如果发生错误/异常,正常的代码流将被中断,代码(包括释放内存)可能无法完成并出现在内存调试工具中。Pascal 的优点之一是(可选的)运行时检查。使用它们。

于 2012-10-14T11:54:08.793 回答