2014-08-28 154 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