2014-08-28 114 views
2

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

下面是一些示例代码。

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“析构函数”的断言,使登录尝试释放一个基于堆栈的堆栈?

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方法。但找不到任何文档。
我希望能够区分堆栈/堆错误和其他指针问题。

编辑:Choosen解决方案
要调试帮助我添加了一个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; 
+0

如果你知道你的最大堆栈大小,你可以使用这样的东西:http://stackoverflow.com/a/2741560/327083看看你在哪里,然后确定地址是否在当前堆栈的范围内。同意@DavidHeffernan记住你,这似乎是一个可疑实用程序的练习... – 2014-08-28 21:45:22

+0

@J ...并非所有栈都有相同的大小 – 2014-08-29 07:40:27

+0

FreeMemory不是处理动态分配记录的正确方法。应该使用例程“Dispose”来代替。 – 2014-08-29 14:13:22

回答

6

你不能切实希望以这种方式区分堆栈和堆内存。你说“堆栈”,但有很多。每个线程一个。你需要检查所有堆栈的保留地址。你将如何找到所有堆栈列表来检查而不会遇到可怕的竞争状态?

根据自动分配在堆栈上还是在堆上动态分配,尝试获取记录的行为方式是愚蠢的。这些行为变化需要由记录的消费者来处理。

让您重新考虑的场景是,您的记录包含在另一个类型(记录或类)内。包含类型的实例可能是堆分配的,但不能释放所包含的记录,即使它驻留在堆上。

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

不要继续沿着你已经开始的死胡同。

+0

并非如此,它只是为了能够追踪错误。我会用'ifdefs'添加一些调试代码/数据,检查记录的历史记录并通知我状态。 – Johan 2014-08-28 20:53:17

+0

你不需要那样做。你的内存管理器会在你释放一个没有动态分配的地址时为你做这件事。 – 2014-08-28 20:55:47

3

您可能可以设计一些机制来确定当前堆栈的“深度”,并将其与堆栈指针结合使用来确定给定地址是否位于堆栈当前占用的范围内。

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

如果我正确地解释你的目标,你想阻止那里有人静态分配的一个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; 

然而,这可以通过不暴露您TMiniStack类,而是使用一个接口,私下实施TMiniStack类作为参考计算的对象,只露出接口和工厂类型来避免既然你已经在使用一个工厂

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

,您consu的影响:

在使用中,这将是这个样子我认为微不足道的,他们不需要关心他们是否或何时应该释放他们分配的小型堆栈 - 引用计数将为他们照顾。

+0

这是合理的建议。但是,我猜想Johan基于他之前的问题特别想要允许堆栈分配,以便进行全面的理由分析。 – 2014-08-29 06:33:03

+0

想要它是不一样的需要它。 ;)我还没有看到他提到的其他问题,并且这个特定问题没有提到所述的,期望的方法专门解决的任何性能问题(可能是过早优化的糟糕情况?):) – Deltics 2014-08-29 07:29:46

+0

原因MiniStack的de'etre是性能。如果它没有明显优于TStack 那么它没有任何用处。我使用树中的ministack来防止递归解决方案。使用正常的TStack将ministack放在适当位置,将运行时间与使用正常TStack的堆栈实现相比减半。它也使得它比递归解决方案稍快。由于隐藏的try-finally,引用计数会在这里杀死性能。如果我保持堆栈而不是在每个函数调用中重新创建堆栈,我会遇到多线程问题。我想我只会将堆栈设置为Stack。 – Johan 2014-08-29 12:49:24