今天來聊聊一些常見的內(nèi)存相關的問題。很多人認為,C語言程序設計中一個最難的部分就是和內(nèi)存操作。因為它過于抽象,很難讓初學者準確把握其特性。我今天在找配圖的時候也很難把malloc出來的buffer和上面這張圖片聯(lián)系起來。這篇文章里,我們通過幾個簡單的題目幫助大家詮釋C語言操作內(nèi)存的相關問題。
1. 棧空間不能外傳
前面的文章中我們講過棧空間和堆空間的區(qū)別,它們有一個非常重要的區(qū)別是棧空間的使用有一個自動的生命周期,而堆空間需要程序員自己通過代碼控制其生命周期。
我們看一下下面這段代碼:
char* fun()
{
char a[60] = "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHI";
char b[60];
strcpy(b, a);
printf("%s\n", b);
return b;
}
int main()
{
char* p = fun();
printf("%s\n", p);
return 0;
}
這段代碼的執(zhí)行結(jié)果如下:
我們發(fā)現(xiàn)第二次打印的時候從fun()中傳出的內(nèi)存空間已經(jīng)被修改過了,其實是被系統(tǒng)回收分配給其他變量了。
char b[60]這句話在棧空間中申請了一個60個字符大小的空間,它的生命周期到函數(shù)結(jié)尾處。因此,當函數(shù)返回時,數(shù)組b的地址已經(jīng)被系統(tǒng)回收了。
因此,我們要記住以下兩點:
- 棧內(nèi)地址不能傳遞到函數(shù)外
- 站內(nèi)地址不需要手動釋放空間
那如果我們一定要從函數(shù)內(nèi)部傳出一段有效內(nèi)存怎么辦呢?我們可以把代碼做如下修改:
char* fun()
{
char a[60] = "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHI";
char* p = (char*)malloc(60 * sizeof(char));
strcpy(p, a);
printf("%s\n", p);
return p;
}
在函數(shù)中申請一段堆空間,這樣就可以在函數(shù)退出后依然有效。但main函數(shù)中需要記著自行是否這段內(nèi)存空間。
int main()
{
char* p = fun();
printf("%s\n", p);
free(p);
return 0;
}
2. 內(nèi)存訪問越界
一說到內(nèi)存訪問我們就會想到用下標或指針訪問內(nèi)存的某個位置時不能訪問到申請大小之外去。但常常有人犯下面這個錯誤:
int main()
{
char a[12] = "Hello World";
char* p = (char*)malloc(15 * sizeof(char));
memcpy(p, a, 5);
printf("%s\n", p);
return 0;
}
這段代碼問題在哪兒呢?先看看執(zhí)行結(jié)果:
本來想打印出Hello這個詞,卻在后面出現(xiàn)了亂碼。其實原因很簡單,我們申請到內(nèi)存之后并沒有初始化,因此這段內(nèi)存中充滿了亂碼。而在我們使用memcpy的時候并沒有把字符串的結(jié)尾標識符拷貝到p指針的內(nèi)存中。于是,在打印時,程序會一直向后找字符串結(jié)束符,這樣不確定因素很多。主要有下面兩個危險:
- 如果在亂碼中恰好有一個字符串結(jié)束符,那么會打印出亂碼
- 如果亂碼中沒有結(jié)束符,那么會一直找下去產(chǎn)生訪問越界,出現(xiàn)不可預測的錯誤
那么正確的做法是什么呢?
- 方法一:內(nèi)存初始化
在申請到內(nèi)存之后立刻用0將它初始化,代碼如下:
int main()
{
char a[12] = "Hello World";
char* p = (char*)malloc(15 * sizeof(char));
memset(p, 0, 15 * sizeof(char));
memcpy(p, a, 5);
printf("%s\n", p);
return 0;
}
- 方法二:拷貝之后加入字符串結(jié)束符
在進行內(nèi)存拷貝之后,人為加入字符串結(jié)束符,代碼如下:
int main()
{
char a[12] = "Hello World";
char* p = (char*)malloc(15 * sizeof(char));
memcpy(p, a, 5);
p[5] = 0;
printf("%s\n", p);
return 0;
}
3. 內(nèi)存釋放順序
經(jīng)常有人在釋放結(jié)構體和成員變量指針時出現(xiàn)因為順序錯誤而產(chǎn)生的內(nèi)存泄露問題。這些問題我們在前面的項目中涉及過。先看一下這段代碼:
typedef struct _tagNode
{
int n;
char* p;
}Node;
int main()
{
Node* pNode = (Node*)malloc(sizeof(Node));
pNode->n = 1;
pNode->p = (char*)malloc(10 * sizeof(char));
strcpy(pNode->p, "Hello");
printf("%s\n", pNode->p);
free(pNode);
free(pNode->p);
return 0;
}
這段代碼執(zhí)行后會出現(xiàn)內(nèi)存泄露,看出是什么原因了嗎?
程序中共有兩個指針,pNode和p。當我們執(zhí)行free(pNode)這句話時,保存p指針變量的空間已經(jīng)被釋放,因此在執(zhí)行free(pNode->p)這句話時就會報錯。正確的做法應該是這樣:
free(pNode->p);
free(pNode);
pNode = NULL;
在釋放內(nèi)存空間時,我們有一個常用的方法:先申請的后釋放。
我是天花板,讓我們一起在軟件開發(fā)中自我迭代。
如有任何問題,歡迎與我聯(lián)系。
上一篇:21天C語言代碼訓練營(第十七天)
下一篇:21天C語言代碼訓練營(第十九天)