如何判断指针是指向堆栈还是指向堆?

时间:2014-08-28 20:41:02

标签: delphi pointers

如果我有一个指向内存的指针,我如何判断它是否指向基于堆的结构(因此应该被释放)或它是否指向堆栈(因此不应该被触及)?

这是一些示例代码。

TMiniStack<T> = record
private
  SP: integer;
  fData: array[0..DefaultStackSize - 1] of T;
public
  procedure Free;
  procedure Push(const Item: T); inline;
  function Pop: T; inline;
end;

StaticFactory<T> = class
public type
  PStack = ^Stack;
  Stack = TMiniStack<T>;
public
  class function Create(Size: integer = DefaultStackSize); static;
end;

implementation

我在Free&#39;析构函数&#39;中添加了什么断言?能够记录尝试释放基于堆栈的堆栈吗?

procedure TMiniStack<T>.Free;
begin
  AssertOrLog(@Self is really on the heap,'This stack does not live on the heap');
  Finalize(Items, Count);
  FreeMemory(@Self);
end;

我似乎记得有IsValidPointer方法。但找不到任何文件。
我希望能够区分堆栈/堆错误和其他指针问题。

编辑:选择解决方案
为了帮助调试,我添加了IsHeapBased: TGUID字段。仅在定义调试时才包含该值。免费方法中的断言检查此并提供反馈。我还添加了一个容量字段(仅调试)来检测堆栈溢出 此检查的目的仅仅是为了帮助调试。

{$IFDEF DEBUG}
MagicHeapFlag: TGUID = '{EF227045-27A9-4EF3-99E3-9D279D58F9A0}';
{$ENDIF}

class function MiniStack<T>.Create(Size: integer = DefaultSize): PStack;
begin
  Result:= AllocMem(SizeOf(TMiniStack<T>) - (DefaultSize * SizeOf(T)) + (Size * SizeOf(T)));
  Result.SP:= 0;
{$IFDEF DEBUG}
  Result.IsHeapBased:= MagicHeapFlag;
  Result.HeapSize:= Size;
{$ENDIF}
end;

{$IFDEF DEBUG}
function TMiniStack<T>.capacity: Integer;
begin
  if IsHeapBased = MagicHeapFlag then begin
    Result:= HeapSize;
  end
  else Result:= DefaultSize;
end;
{$ENDIF}

procedure TMiniStack<T>.Free;
begin
{$IFDEF DEBUG}
  Assert(IsHeapBased = MagicHeapFlag, 'Do not call free on stack based MiniStacks');
{$ENDIF}
  Finalize(Items, count);
  FreeMem(@Self);
end;

2 个答案:

答案 0 :(得分:6)

您无法真实地希望以这种方式区分堆栈和堆内存。你说“堆栈”,但有很多。每个线程一个。您需要检查所有堆栈的保留地址。如何在不遇到可怕的竞争条件的情况下找到所有堆栈的列表进行检查?

尝试使记录行为不同取决于它是在堆栈上自动分配还是在堆上动态分配,这是愚蠢的。这些行为变化需要由记录的消费者处理。

让您重新考虑的方案是您的记录包含在另一种类型(记录或类)中的位置。包含类型的实例可能是堆分配的,但您不能释放包含的记录,即使它驻留在堆上。

堆分配的底线是您需要记住分配堆的时间并确保释放堆上分配的任何内容。如果你忘记了谁通过指针拥有内存你做错了。

不要继续沿着已经开始的死路。

答案 1 :(得分:3)

你可能会设想一些机制来确定当前的堆栈&#34;深度&#34;并将其与堆栈指针结合使用,确定给定地址是否位于堆栈当前占用的范围内。

但是在实现和运行时执行方面,这可能是很多工作。

如果我正确地解释了您的目标,您希望防止有人在堆栈上静态分配 TMiniStack ,然后不恰当地调用 Free 方法。

我建议如果这是一个可能需要避免的情况,那么实现这一目标的最简单方法就是消除它可能发生的可能性。

对我来说,我会选择静态分配或动态,但不允许两者。使用记录类型不可能阻止动态分配,因此通过选择记录,您必须接受此操作。在这种情况下提供&#34; Free&#34;记录类型的方法是错误的,并且危险地误导了记录类型的消费者。

另一种方法是使用类而不是记录来防止静态分配。但这需要 TMiniStack 的消费者始终负责分配和正确解除分配迷你堆栈。

但是,可以通过不暴露 TMiniStack 类来代替使用接口,将 TMiniStack 类私有地作为引用计数对象实现,从而仅暴露接口和工厂类型:

  interface

  type
    IMiniStack<T> = interface
      procedure Push(aValue: T);
      function Pop: T;
    end;

    MiniStack<T> = class
      class function Create(aSize: Integer): IMiniStack<T>; reintroduce;
    end;


  implementation

  type
    TMiniStack<T> = class(TInterfacedObject, IMiniStack<T>)
    private
      fItems: array of T;
    protected
      constructor Create(aSize: Integer);
      procedure Push(aValue: T);
      function Pop: T;
    end;


  constructor TMiniStack<T>.Create(aSize: Integer);
  begin
    inherited Create;
    SetLength(fItems, aSize);
  end;

  function TMiniStack<T>.Pop: T;
  begin
    // left as exercise for the reader
  end;

  procedure TMiniStack<T>.Push(aValue: T);
  begin
    // left as exercise for the reader
  end;


  class function MiniStack<T>.Create(aSize: Integer): IMiniStack<T>;
  begin
    result := TMiniStack<T>.Create(aSize);
  end;

在使用中,这看起来像这样:

procedure TForm1.FormCreate(Sender: TObject);
var
  ms: IMiniStack<Integer>;
begin
  ms := MiniStack<Integer>.Create(100);
end;

由于您已经在使用工厂,我认为对您的消费者的影响可以忽略不计,他们不需要关心他们是否或何时应该释放他们分配的迷你堆栈 - 引用计数将照顾那对他们来说。