簡體   English   中英

這是釋放內存的好方法嗎?

[英]Is this a good way to free memory?

下面給出了釋放struct Foo實例的struct Foo

void DestroyFoo(Foo* foo)
{
    if (foo) free(foo);
}

我的一位同事建議以下內容:

void DestroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL; // prevents future concurrency problems
    memset(tmpFoo, 0, sizeof(Foo));  // problems show up immediately if referred to free memory
    free(tmpFoo);
}

我看到釋放后將指針設置為NULL更好,但是我不確定以下內容:

  1. 我們真的需要將指針分配給一個臨時指針嗎? 它對並發和共享內存有幫助嗎?

  2. 將整個塊設置為0以迫使程序崩潰或至少輸出具有明顯差異的結果真的是個好主意嗎?

我們真的需要將指針分配給一個臨時指針嗎? 它對並發和共享內存有幫助嗎?

它與並發或共享內存無關。 沒有用。

將整個塊設置為0以迫使程序崩潰或至少輸出具有明顯差異的結果真的是個好主意嗎?

一點都不。

您的同事建議的解決方案很糟糕。 原因如下:

  • 將整個塊設置為0也無法實現。 因為有人不小心使用了free()塊,所以基於該塊的值他們不會知道這一點。 這就是calloc()返回的塊。 因此,不可能知道它是新分配的內存( calloc()還是malloc()+memset() ),還是之前代碼中的free()所分配的內存。 如果有的話,對您的程序來說,將free()的每個內存塊清零是額外的工作。

  • free(NULL); 是明確定義的,並且是空操作,因此if(ptr) {free(ptr);}if條件什么都不會實現。

  • 由於free(NULL); 是無操作的,將指針設置為NULL實際上會隱藏該錯誤,因為如果某個函數實際上在已經具有free()的指針上調用free() ,那么他們將不知道。

  • 大多數用戶功能在開始時都會進行NULL檢查,並且可能不會考慮將NULL作為錯誤條件傳遞給它:

void do_some_work(void *ptr) {
    if (!ptr) {
        return; 
    }

   /*Do something with ptr here */
}

因此,所有這些額外的檢查和歸零都會給人一種“健壯”的假感覺,而實際上並沒有任何改善。 它只是將一個問題替換為另一個問題,從而增加了性能和代碼膨脹的成本。

因此,只需調用free(ptr); 沒有任何包裝的功能是既簡單又強大的(大部分malloc()實現將立即崩潰雙免費的,這是一件好事 )。

要“偶然地”調用free()兩次或更多次,沒有簡單的方法。 跟蹤所有分配的內存並適當地free()它是程序員的責任。 如果有人覺得這很難處理,那么C可能不是適合他們的語言。

如果函數被兩次調用,您的同事建議將使代碼“更安全”(請參見sleske注釋...“對於每個人來說,“更安全”可能並不意味着相同... ;-)。

使用您的代碼,這很可能會崩潰:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
DestroyFoo(foo); // will call free on memory already freed

使用您同事的代碼版本,這不會崩潰:

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(&foo);
DestroyFoo(&foo); // will have no effect

現在,對於這種特定情況,執行tmpFoo = 0; (在DestroyFoo內)就足夠了。 memset(tmpFoo, 0, sizeof(Foo)); 如果Foo具有釋放內存后可能錯誤訪問的額外屬性,則將防止崩潰。

所以我會說是的,這樣做可能是一個好習慣。...但這只是針對具有不良習慣的開發人員的一種安全性(因為絕對沒有理由在沒有重新分配它的情況下兩次調用DestroyFoo ...最后,您使DestroyFoo更加“安全”,但速度較慢(它會做更多的工作以防止DestroyFoo用)。

第二種解決方案似乎設計過度。 當然,在某些情況下它可能更安全,但是開銷和復雜性太大。

如果要安全起見,應該在釋放內存后將指針設置為NULL。 這始終是一個好習慣。

Foo* foo = malloc( sizeof(Foo) );
DestroyFoo(foo);
foo = NULL;

而且,我不知道為什么人們在調用free()之前先檢查指針是否為NULL。 這是不需要的,因為free()將為您完成這項工作。

僅在某些情況下,將內存設置為0(或其他方式)只是一種好習慣,因為free()不會清除內存。 它只會將內存區域標記為空閑,以便可以重用。 如果要清除內存,以便沒人能讀取它,則需要手動清潔它。 但這是非常繁重的操作,因此,不應使用它來釋放所有內存。 在大多數情況下,無需清除就可以釋放,而不必為了執行不必要的操作而犧牲性能。

void destroyFoo(Foo** foo)
{
    if (!(*foo)) return;
    Foo *tmpFoo = *foo;
    *foo = NULL;
    memset(tmpFoo, 0, sizeof(Foo));
    free(tmpFoo);
}

您的同事代碼不正確,因為

  • 如果fooNULL它將崩潰
  • 創建其他變量毫無意義
  • 將值設置為零沒有任何意義
  • 如果直接釋放結構包含必須釋放的內容,則無法正常工作

我認為您的同事可能會想到此用例

Foo* a = NULL;
Foo* b = createFoo();

destroyFoo(NULL);
destroyFoo(&a);
destroyFoo(&b);

在這種情況下,應該是這樣。 試試這里

void destroyFoo(Foo** foo)
{
    if (!foo || !(*foo)) return;
    free(*foo);
    *foo = NULL;
}

首先,我們需要看一下Foo ,讓我們假設它看起來像這樣

struct Foo
{
    // variables
    int number;
    char character;

    // array of float
    int arrSize;
    float* arr;

    // pointer to another instance
    Foo* myTwin;
};

現在定義它應該如何銷毀,讓我們首先定義它應該如何創建

Foo* createFoo (int arrSize, Foo* twin)
{
    Foo* t = (Foo*) malloc(sizeof(Foo));

    // initialize with default values
    t->number = 1;
    t->character = '1';

    // initialize the array
    t->arrSize = (arrSize>0?arrSize:10);
    t->arr = (float*) malloc(sizeof(float) * t->arrSize);

    // a Foo is a twin with only one other Foo
    t->myTwin = twin;
    if(twin) twin->myTwin = t;

    return t;
}

現在我們可以寫一個與create函數相反的destroy函數

Foo* destroyFoo (Foo* foo)
{
    if (foo)
    {
        // we allocated the array, so we have to free it
        free(t->arr);

        // to avoid broken pointer, we need to nullify the twin pointer
        if(t->myTwin) t->myTwin->myTwin = NULL;
    }

    free(foo);

    return NULL;
}

在這里嘗試嘗試

int main ()
{
    Foo* a = createFoo (2, NULL);
    Foo* b = createFoo (4, a);

    a = destroyFoo(a);
    b = destroyFoo(b);

    printf("success");
    return 0;
}

不幸的是,這個想法是行不通的。

如果目的是要獲得雙重釋放,則不包括以下情況。

假設此代碼:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
free (ptr_1);
free (ptr_2); /* This is a bug */

建議改寫為:

Foo *ptr_1 = (FOO*) malloc(sizeof(Foo));
Foo *ptr_2 = ptr_1;
DestroyFoo (&ptr_1);
DestroyFoo (&ptr_2); /* This is still a bug */

問題在於對DestroyFoo()的第二次調用仍然會崩潰,因為ptr_2不會重置為NULL,並且仍然指向已經釋放的內存。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM