目錄
- 1. 前言
- 2. 正文
- 2.1. “分配” 與 “釋放”
- 2.2. 運行測驗
- 2.2.1. VSCode 下使用 gcc 編譯
- 2.2.2. VS2022 下使用 MSVC 編譯
- 2.3. 程式漏洞測驗
- 2.4. 程式漏洞修復
- 2.5. 附加測驗
- 3. 總結
歡迎大家移步 我的博客 查看原文,
1. 前言
上機時遇到如下 C++ 代碼 ( C 代碼):
//洗掉帶頭結點的多項式單鏈表中系數為 0 項
void DelZero(PolyNode *&L)
{
PolyNode *pre = L, *p = pre->next;
while (p != NULL)
{
if (p->coef == 0.0)
{
pre->next = p->next;
free(p);
}
pre = p;
p = p->next;
}
}
來源:《資料結構教程 (第 5 版) 上機實驗指導》(李春葆主編) 第二章實驗題 10
當 if 陳述句執行后,指標 p 指向的記憶體已經被釋放,接著又執行 p = p->next,使用了指向已經被釋放了的記憶體的指標,那么,這樣的用法是否正確呢?
2. 正文
free(p) 后 p = p->next 能否正確執行,要看編譯器的具體實作,不同的編譯器可能會產生不同的結果,上述程式我在 VSCode 中使用 gcc 編譯能正常運行,但使用 VS2022 的 MSVC 編譯后不能正常運行,而且即便能夠正常運行,這種用法也會導致程式出現漏洞,
2.1. “分配” 與 “釋放”
在呼叫記憶體分配函式 (\(malloc、calloc\) 等) 在堆區分配一塊記憶體后,這塊記憶體會被標記為 已使用,確保在之后的記憶體分配中不會將這塊已經分配過的記憶體進行二次分配,
而呼叫 free 函式將記憶體釋放后,這塊記憶體便會被標記為 未使用,在往后的記憶體分配中這塊記憶體便能再次被分配了,而 free 后記憶體中的值是否改變這要看編譯器的具體實作,不同編譯器可能具有不同的實作方式,
2.2. 運行測驗
2.2.1. VSCode 下使用 gcc 編譯
在呼叫 free 函式釋放動態分配的記憶體后,指標 p 仍然指向這塊已經被釋放的記憶體 (指標變數 p 中仍然保存著這塊記憶體的地址),而使用 gcc 進行編譯,被釋放的記憶體中原有的內容并未被覆寫 (前言中給出的代碼對應的程式是這樣,一會兒會舉個反例),執行 p = p->next 后 p 指向單鏈表的下一個結點,因此程式能正常運行而不會錯誤中止,雖然能正常運行,但程式存在漏洞,
參見 2.3 程式漏洞測驗
2.2.2. VS2022 下使用 MSVC 編譯
在 VS2022 下使用 MSVC 進行編譯,第一次執行 free(p) 前 p 指向的記憶體中的內容如下:

第一次執行 free(p) 后,p 指向的記憶體中的內容被覆寫 (紅色部分),這時再執行 p = p->next 便會產生錯誤,使程式卡死在這一步,


2.3. 程式漏洞測驗
我們舉一個例子來說明在能正常運行的前提下 (gcc 編譯),前言中的程式存在的漏洞,
- 根據多項式 \(x^9 + 0x^5 + 0x^3 + 3x^2 + 0x\) 創建一個多項式單鏈表 (如下, 這里為了方便只寫出系數),

pre和p初始指向如圖,

- 此時
p != NULL,進行第 \(1\) 次回圈,p->coef == 1,兩指標向后移,

- 此時
p != NULL,進行第 \(2\) 次回圈,p->coef == 0, 洗掉p指向的結點后兩指標向后移,整理一下這一步得到的單鏈表,


- 此時
p != NULL,進行第 \(3\) 次回圈,p->coef == 0, 這時本應洗掉p指向的結點后兩指標向后移,但此時pre指標指向的是已經被釋放的結點,pre->next = p->next改變的是已釋放結點的next域指向,從而導致 漏刪 了一個本應洗掉的結點,

- 此時
p != NULL,進行第 \(4\) 次回圈,p->coef != 0, 兩指標向后移,

- 此時
p != NULL,進行第 \(5\) 次回圈,p->coef == 0, 洗掉p指向的結點后兩指標向后移,

- 此時
p == NULL,回圈結束,最終得到的單鏈表如圖,

測驗代碼如下,這里列印出 free(p) 執行前后指標變數 p 中內容以及 p 指向的記憶體中的內容,
//程式漏洞測驗代碼 (僅列出與漏洞有關代碼)
void DelZero(PolyNode *&L) //洗掉系數為零的項
{
PolyNode *pre = L, *p = pre->next;
while (p != NULL)
{
if (p->coef == 0.0)
{
pre->next = p->next;
#ifdef DEBUG
printf("free(p) 前, p = %p, p->coef = %f, "
"p->exp = %d, p->next = %p\n", p, p->coef, p->exp, p->next);
#endif
free(p);
#ifdef DEBUG
printf("free(p) 后, p = %p, p->coef = %f, "
"p->exp = %d, p->next = %p\n\n", p, p->coef, p->exp, p->next);
#endif
p = pre->next;
continue;
}
pre = p;
p = p->next;
}
}
int main()
{
PolyArray a[] = {{1, 9}, {0, 5}, {0, 3}, {3, 2}, {0, 1}};
PolyNode *L;
int n = 5;
CreatePolyR(L, a, 5);
printf("原多項式為: ");
DispPoly(L);
printf("洗掉系數為 0 項后為: \n\n");
DelZero(L);
DispPoly(L);
DestroyPoly(L);
return 0;
}
執行結果如圖所示:

可以看到,free(p) 操作執行前后,指標變數 p 中內容以及 p 指向的記憶體中的內容均未發生改變,所以在 free(p) 后執行 p = p->next 編譯器并不會報錯,但卻給程式帶來了邏輯上的漏洞,
2.4. 程式漏洞修復
//洗掉帶頭結點的多項式單鏈表中系數為 0 項 (Beta)
void DelZero(PolyNode *&L)
{
PolyNode *pre = L, *p = pre->next;
while (p != NULL)
{
if (p->coef == 0.0)
{
pre->next = p->next;
free(p);
+ p = pre->next;
+ continue;
}
pre = p;
p = p->next;
}
}
漏洞修復后運行結果如圖:

2.5. 附加測驗
在 VSCode 下使用 gcc 編譯以下 C 代碼:
//測驗 1
#include <stdio.h>
#include <malloc.h>
int main()
{
int *p = (int *) malloc (sizeof(int));
*p = 9;
printf("p = %p\n", p);
printf("free(p) 前 *p = %d\n", *p);
printf("-----------------\n");
free(p);
printf("p = %p\n", p);
printf("free(p) 后 *p = %d\n", *p);
printf("------------------------\n");
*p = 9;
printf("p = %p\n", p);
printf("free(p) 后再執行 *p = 9, 此時 *p = %d\n\n", *p);
return 0;
}
測驗 \(1\) 運行結果如下:

//測驗 2
#include <stdio.h>
#include <malloc.h>
int main()
{
char *q = (char *) malloc (20 * sizeof(char));
q = "Hello World";
printf("q = %p\n", q);
printf("free(q) 前 q 指向記憶體中的內容為: %s\n", q);
printf("--------------------------------------------\n");
free(q);
printf("q = %p\n", q);
printf("free(q) 后 q 指向記憶體中的內容為: %s\n", q);
printf("--------------------------------------------\n");
q = "Hello World";
printf("q = %p\n", q);
printf("free(q) 后再執行 q = \"Hello World\", 此時 q 指向記憶體中的內容為: %s\n\n", q);
return 0;
}
測驗 \(2\) 運行結果如下:

可以看到,測驗 \(1\) 中 free(p) 后 p 指向記憶體中的內容發生了改變,而 測驗 \(2\) 中 free(q) 后 q 指向記憶體中的內容并沒有發生變化,這又是為什么呢?難道 gcc 編譯器對于是否覆寫 free 后的記憶體中的內容還有什么規則嗎?這個疑問暫時得不到解答,留待日后弄清,
3. 總結
呼叫 free 函式釋放記憶體后,原先指向這塊記憶體的指標 p 仍然指向這塊記憶體,不過這時候的指向已經是不合法的了,如果這塊記憶體被分配用作其他用途,這時再次參考指標 p (當做 free(p) 之前的用途來使用) 就有可能引發未知的錯誤 (前言中的程式盡管在 gcc 編譯下能夠正常運行,并且記憶體釋放后也沒有再分配,但參考指向已釋放記憶體的指標 p 還是使程式產生了漏洞),所以,在 free(p) 后,最好不要出現使用指向已釋放記憶體的指標 p 的情況,必要時應將 p 置為 NULL,
參考資料
-
動態記憶體分配,C語言動態記憶體分配詳解
-
malloc和free函式使用注意事項,C語言malloc和free使用詳解
-
(C語言記憶體十八)malloc函式背后的實作原理——記憶體池
-
ZH奶酪:C語言中malloc()和free()函式決議
-
C語言free釋放記憶體后為什么指標里的值不變?竟然還可以輸出?
-
C語言中free掉動態記憶體后原本指向這塊記憶體的指標和記憶體里的值會有什么變化
本文來自博客園,作者:chenxin5,轉載請注明原文鏈接:https://www.cnblogs.com/chenxin5/p/16576905.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/501611.html
標籤:其他
上一篇:Rust基本資料型別
下一篇:day19--Java集合02
