[英]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
更好,但是我不確定以下內容:
我們真的需要將指針分配給一個臨時指針嗎? 它對並發和共享內存有幫助嗎?
將整個塊設置為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);
}
您的同事代碼不正確,因為
foo
為NULL
它將崩潰 我認為您的同事可能會想到此用例
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.