【问题标题】:Writing a Scheme interpreter with FPC: Allocation and Pointers使用 FPC 编写 Scheme 解释器:分配和指针
【发布时间】:2013-01-05 21:56:26
【问题描述】:

作为一名历史学家,写Scheme interpreter in FPC 已经在第一阶段对我来说是一项严肃的任务。 :) 我在关注 Peter Michaux 的博客,who showed how to do it in C(也有翻译为Ada,可能对 Pascal 有帮助)。

考虑一下 Michaux 工作 (v 0.1) 中的 C 语言中的这两个函数:

object *alloc_object(void) {
    object *obj;

    obj = malloc(sizeof(object));
    if (obj == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }
    return obj;
}

object *make_fixnum(long value) {
    object *obj;

    obj = alloc_object();
    obj->type = FIXNUM;
    obj->data.fixnum.value = value;
    return obj;
}

据我了解(只是C中的基本阅读知识),构造函数make_fixnum返回一个指向结构的指针(类型为fixnum的标记数据);对于构造的对象,必须分配内存(感谢@David Heffernan 昨天的point)。

这是我目前对 FPC 的翻译,编译没有任何错误:

program scheme;

type
   TTag = (ScmFixnum);
   PScmObject = ^TScmObject;
   TScmObject = record
      case ScmObjectTag: TTag of
         ScmFixnum: (ScmObjectFixnum: integer);
      end;

var Test: PScmObject = nil;

procedure AllocateObject(x: PScmObject);
begin
    new(x);
end;

function MakeFixnum(x: integer): PScmObject;
var
   fixnum: PScmObject = nil;
begin
   AllocateObject(fixnum);
   fixnum^.ScmObjectTag := ScmFixnum;
   fixnum^.ScmObjectFixnum := x;
   MakeFixnum := fixnum;
end;

begin
   Test := MakeFixnum(1);
   writeln(Test^.ScmObjectTag);
   writeln(Test^.ScmObjectFixnum);
end.

但是……:

$ ./test 
Runtime error 216 at $080480DD
  $080480DD
  $08048117
  $08063873

我怀疑,我使用和引用指针的方式存在严重缺陷。

非常感谢任何帮助我理解指针和内存的工作原理的人(也欢迎参考常见问题解答、论文等)。

【问题讨论】:

  • AllocateObject 真的应该是一个函数

标签: delphi scheme pascal freepascal sicp


【解决方案1】:

您的 AllocateObject 函数是错误的。它在变量 x 中创建一个新对象,但它不会将创建的对象传递给调用函数,因为它是按值调用的。如果您更改调用约定,它会起作用:

 procedure AllocateObject(out x: PScmObject);
 begin 
    new(x);
 end;

如果您在调试器中查看 fixnum 变量,您可以看到它保持为 nil。



与您的问题无关,我认为在口译员中使用记录不是一个好主意。它很快变成了内存管理的噩梦(至少发生在我写的一个解释器接近 20 kloc 时,我不得不按如下方式替换记录:)

代替你的记录

 PScmObject = ^TScmObject;
 TScmObject = record
    case ScmObjectTag: TTag of
       ScmFixnum: (ScmObjectFixnum: integer);
    end;

您可以使用类,例如:

TScmObject = class()
  function Tag: TTag; virtual; abstract;
  function Fixnum: integer; virtual; abstract;
end;
TScmObjectFixNum = class(TScmObject)
  function Tag: TTag; override;
  function Fixnum: integer; override;
private
  value: integer;
end;

function TScmObjectFixNum.Tag: TTag;
begin
  result := ScmFixnum; 
end;
function TScmObjectFixNum.Fixnum: integer; 
begin
  result := value; 
end;

然后您可以轻松地创建它

 var x: TScmObject;
 x := TScmObjectFixNum.create() ;
 if x.tag = scmfixnum (* or x is TScmObjectFixNum *) then
    ... x.scmfixnum ...
 x.free

如果您的方案实现中没有循环引用,您甚至可以使用接口。然后它被引用计数并自动释放:

IScmObject = interface
  function Tag: TTag;
  function Fixnum: integer;
end;
TScmObject = class(TInterfacedObject, IScmObject)
  function Tag: TTag; virtual; abstract;
  function Fixnum: integer; virtual; abstract;
end;
TScmObjectFixNum = class(TScmObject)
  function Tag: TTag; override;
  function Fixnum: integer; override;
private
  value: integer;
end;



 var x: IScmObject;
 x := TScmObjectFixNum.create() ;
 if x.tag = scmfixnum (* or x is TScmObjectFixNum *) then
    ... x.scmfixnum ...
 //x.free no longer necessary (or allowed)!

【讨论】:

  • 非常感谢您的努力!您的类实现似乎很简单,我会尝试的。还有一个问题:您所说的“循环引用”到底是什么意思?
  • 通过接口,当没有引用指向对象时,对象会被自动删除。因此,您可以编写 var a,b: ISomething; a := TSomething.create; b := a; a := nil; 并拥有指向 b 中对象的唯一指针。如果你写b := nil;,对象就会被销毁。 |但是如果有一个循环引用,即指向对象的东西和指向不起作用的东西的对象,因为引用永远不会全部为零。例如。如果你已经实现了 cons,你可以有b = cons(b, b),然后 b 指向它自己并且永远不会被破坏。(但是如果你的 cons 复制它就可以了)
猜你喜欢
  • 2012-12-19
  • 1970-01-01
  • 2011-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-31
  • 1970-01-01
相关资源
最近更新 更多