C语言再学习 -- 再论内存管理
之前有總結(jié)過內(nèi)存管理,參看:C語言再學(xué)習(xí) -- 內(nèi)存管理
但現(xiàn)在看來,缺少示例。從新再寫一篇文章,著重介紹常見內(nèi)存錯誤、跨函數(shù)使用存儲區(qū)。開始吧,再論內(nèi)存管理!!
發(fā)生內(nèi)存錯誤是件非常麻煩的事情。編譯器不能自動發(fā)現(xiàn)這些錯誤,通常是在程序運(yùn)行時才能捕捉到。而這些錯誤大多沒有明顯的癥狀時隱時現(xiàn)增加了改錯的難度。
參看:《高質(zhì)量C++ C編程指南》.林銳
一、常見的內(nèi)存錯誤及其對策
1、內(nèi)存分配未成功,卻使用了它。
編程新手常犯這種錯誤,因為他們沒有意識到內(nèi)存分配會不成功。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。如果指針 p 是函數(shù)的參數(shù),那么在函數(shù)的入口處用 assert (p != NULL) 進(jìn)行檢查。如果是用 malloc 或 new 來申請內(nèi)存,應(yīng)該用 if (p == NULL) 或者 if (p != NULL) 進(jìn)行防錯處理。
#include <stdio.h> #include <stdlib.h> #include <string.h>int main (void) {char *p = (char*)malloc (20 * sizeof (char));if (p == NULL) //必須檢查是否分配成功{perror ("error"),exit (1);}strcpy (p, "hello world");puts (p);free (p);p = NULL;return 0; } 輸出結(jié)果: hello world
2、內(nèi)存分配雖然成功,但是尚未初始化就引用它
犯這種錯誤主要有兩個原因:一是沒有初始化的觀念,二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯誤(例如數(shù)組)。
內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),盡管有些時候 為零值,我們寧可信其無不可信其有。所以無論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
#include <stdio.h> #include <string.h>int main(void) {int buffer[5] = {1, 2, 3, 4, 5}, i;memset(buffer, 0, sizeof(buffer));//將buffer元素全置為0for (i = 0; i < 5; ++i){printf ("%d ", buffer[i]);}printf ("\n");return 0; } 輸出結(jié)果: 0 0 0 0 0
3、忘記了釋放內(nèi)存,造成內(nèi)存泄漏
含有這種錯誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時系統(tǒng)的內(nèi)存充足,你看不到錯誤。終有一次程序突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。
這里還需要了解如何檢查內(nèi)存泄漏,參看:百度百科 -- 內(nèi)存泄漏
動態(tài)內(nèi)存的申請與釋放必須配對,程序中 malooc 與 free 的使用次數(shù)一定要相同,否則肯定有錯誤 (new/delete同理)。
#include <stdio.h> #include <stdlib.h> #include <string.h>int main (void) {while (1) {char *p = (char*)malloc (10000000 * sizeof (char));if (p == NULL) //必須檢查是否分配成功{perror ("error"),exit (1);}strcpy (p, "hello world");puts (p);} // free (p); // p = NULL;return 0; } 輸出結(jié)果: hello world hello world 。。。 hello world hello world error: Cannot allocate memory
4、釋放了內(nèi)存卻繼續(xù)使用它
有三種情況:
(1)程序中的對象調(diào)用關(guān)系過于復(fù)雜,實(shí)在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存,此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面。
(2)函數(shù)的 return 語句寫錯了,注意不要返回指向棧內(nèi)存“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)束時自動銷毀。
(3)使用 free 或 delete 釋放了內(nèi)存后,沒有將指針設(shè)置為 NULL。導(dǎo)致產(chǎn)生“野指針”。
#include <stdio.h> #include <stdlib.h> #include <string.h>int main (void) {char *p = (char*)malloc (20 * sizeof (char));if (p == NULL) //必須檢查是否分配成功{perror ("error"),exit (1);}free (p);strcpy (p, "hello world");puts (p);free (p); //釋放了兩次p = NULL;return 0; } 輸出結(jié)果: hello world *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0828b008 *** 段錯誤 (核心已轉(zhuǎn)儲)
總結(jié):
函數(shù)用法:
type *p;
p = (type*)malloc(n * sizeof(type));
if(NULL == p)
/*請使用if來判斷,這是有必要的*/
{
? ? perror("error...");
? ? exit(1);
}
.../*其它代碼*/
free(p);
p = NULL;/*請加上這句*/
函數(shù)使用需要注意的地方:
1、malloc 函數(shù)返回的是 void * 類型,必須通過 (type *) 來將強(qiáng)制類型轉(zhuǎn)換。
2、malloc 函數(shù)的實(shí)參為 sizeof(type),用于指明一個整型數(shù)據(jù)需要的大小。
3、申請內(nèi)存空間后,必須檢查是否分配成功
4、當(dāng)不需要再使用申請的內(nèi)存時,記得釋放,而且只能釋放一次。如果把指針作為參數(shù)調(diào)用free函數(shù)釋放,則函數(shù)結(jié)束后指針成為野指針(如果一個指針既沒有捆綁過也沒有記錄空地址則稱為野指針),所以釋放后應(yīng)該把指向這塊內(nèi)存的指針指向NULL,防止程序后面不小心使用了它。
5、要求malloc和free符合一夫一妻制,如果申請后不釋放就是內(nèi)存泄漏,如果無故釋放那就是什么也沒做。釋放只能一次,如果釋放兩次及兩次以上會出現(xiàn)錯誤(釋放空指針例外,釋放空指針其實(shí)也等于啥也沒做,所以釋放空指針釋放多少次都沒有問題)。
二、跨函數(shù)使用存儲區(qū)
之前培訓(xùn)的時候有講到,但是看書總結(jié)的時候,大多分開來講了。現(xiàn)在來詳細(xì)講解下:
跨函數(shù)使用的三種情況:
(1)被調(diào)用函數(shù)可以使用調(diào)用函數(shù)的存儲區(qū),指針作形參可以讓被調(diào)用函數(shù)使用其他函數(shù)的存儲區(qū)。
#include <stdio.h> char* fa(char* p_str) {char* p=p_str;p="hello world";return p; }int main() {char* str=NULL;printf("%s\n",fa(str));return 0; }
(2)動態(tài)分配內(nèi)存也可以實(shí)現(xiàn)跨函數(shù)使用存儲區(qū),只要動態(tài)分配函數(shù)內(nèi)存沒有被釋放就可以被任何函數(shù)使用。
#include <stdio.h> #include <stdlib.h> #include <string.h> void foo (char *p) {strcpy (p, "hello");puts (p); }int main (void) {char *p = (char*)malloc (20 * sizeof (char));strcpy (p, "hello world");if (p == NULL){return ;}strcpy (p, "hello world");puts (p);foo (p);free (p);p = NULL;return 0; } 輸出結(jié)果: hello world hello
(3)static 靜態(tài)局部變量的存儲區(qū)可以被任意使用。
#include <stdio.h> int i = 20; //全局變量 void func (void) {static int i = 10; //靜態(tài)/局部變量i++;printf("%d\n",i); } int main (void) {func (); //11 優(yōu)先使用局部變量printf ("%d\n",i); //20 func (); //12 靜態(tài)局部變量跨函數(shù)使用存儲區(qū) return 0; } 輸出結(jié)果: 11 20 12
三、討論malloc(0)返回值
參看:關(guān)于malloc(0)的返回值問題
下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)?
puts("Got a null pointer");
else
puts("Got a valid pointer");?
討論 malloc(0)#include <stdio.h> #include <stdlib.h>int main (void) {char *ptr;if ((ptr = (char *)malloc(0)) == NULL) puts("Got a null pointer");elseputs("Got a valid pointer"); return 0; } 輸出結(jié)果: Got a valid pointer man malloc 查看:
The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free(). 翻譯就是,就是傳個0的話,返回值要么是NULL,要么是一個可以被free調(diào)用的唯一的指針。
用一個測試示例來說明:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <malloc.h>int alloc_memory(char *p , int size) {printf("\nbefore malloc %p\n",p);p = (char *)malloc(size);if(!p){printf("malloc error \n");return -1;}//len of malloc(0)printf("len of malloc(%d) is %d ,the ture is %d\n",size,strlen(p),malloc_usable_size(p));//the first member printf("the first member of malloc(%d) is %p:%d \n",size,p,*p);//set the first member*p = 10;printf("set the first member of malloc(%d) is %p:%d \n",size,p,*p);//memcpymemset(p,'\0',12);memcpy(p,"01234567890123456789",12);printf("after memcpy , the content is %s len is %d , the ture is %d \n",p,strlen(p),malloc_usable_size(p));free(p);p = NULL;printf("\n"); }int main(int argc ,char **argv) {int size = -1;char *p = NULL;//malloc(0)size = 0;alloc_memory(p,size);//malloc(5)size = 5;alloc_memory(p,size);//malloc(20)size = 20;alloc_memory(p,size);return 0; } 輸出結(jié)果: before malloc (nil) len of malloc(0) is 0 ,the ture is 12 the first member of malloc(0) is 0x932f008:0 set the first member of malloc(0) is 0x932f008:10 after memcpy , the content is 012345678901 len is 15 , the ture is 12 before malloc (nil) len of malloc(5) is 0 ,the ture is 12 the first member of malloc(5) is 0x932f008:0 set the first member of malloc(5) is 0x932f008:10 after memcpy , the content is 012345678901 len is 15 , the ture is 12 before malloc (nil) len of malloc(20) is 0 ,the ture is 20 the first member of malloc(20) is 0x932f018:0 set the first member of malloc(20) is 0x932f018:10 after memcpy , the content is 012345678901 len is 12 , the ture is 20 從測試結(jié)果來看,可以得出以下幾個結(jié)論:
1. malloc(0)在我的系統(tǒng)里是可以正常返回一個非NULL值的。這個從申請前打印的before malloc (nil)和申請后的地址0x9e78008可以看出來,返回了一個正常的地址。
2. malloc(0)申請的空間到底有多大不是用strlen或者sizeof來看的,而是通過malloc_usable_size這個函數(shù)來看的。---當(dāng)然這個函數(shù)并不能完全正確的反映出申請內(nèi)存的范圍。
3. malloc(0)申請的空間長度不是0,在我的系統(tǒng)里它是12,也就是你使用malloc申請內(nèi)存空間的話,正常情況下系統(tǒng)會返回給你一個至少12B的空間。這個可以從malloc(0)和malloc(5)的返回值都是12,而malloc(20)的返回值是20得到。---其實(shí),如果你真的調(diào)用了這個程序的話,會發(fā)現(xiàn),這個12確實(shí)是”至少12“的。
4. malloc(0)申請的空間是可以被使用的。這個可以從*p = 10;及memcpy(p,"01234567890123456789",12);可以得出。
總結(jié):為了安全起見,malloc(0)的非NULL返回值,最好不要進(jìn)行除了free()之外的任何操作!
總結(jié)
以上是生活随笔為你收集整理的C语言再学习 -- 再论内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Centos7 重启网卡
- 下一篇: 数据结构与算法 -- 再论递归