C语言第十三课,动态内存分配
動態(tài)內(nèi)存分配的空間放在堆區(qū)。動態(tài)內(nèi)存函數(shù)主要有:malloc,calloc,realloc
動態(tài)內(nèi)存函數(shù)的介紹
malloc
申請一個空間,大小是size的大小,指向的一個類型不明,因為在設(shè)計的時候,不知道用戶是需要什么類型的數(shù)據(jù)。
如果開辟成功,則返回一個指向開辟好空間的指針。
如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查。
calloc
函數(shù)的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個字節(jié)初始化為0。
與函數(shù) malloc 的區(qū)別只在于 calloc 會在返回地址之前把申請的空間的每個字節(jié)初始化為全0。
realloc
realloc函數(shù)的出現(xiàn)讓動態(tài)內(nèi)存管理更加靈活。
有時會我們發(fā)現(xiàn)過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時
候內(nèi)存,我們一定會對內(nèi)存的大小做靈活的調(diào)整。那 realloc 函數(shù)就可以做到對動態(tài)開辟內(nèi)存大小
的調(diào)整。
memblock 是要調(diào)整的內(nèi)存地址
size 調(diào)整之后新大小
返回值為調(diào)整之后的內(nèi)存起始位置。
這個函數(shù)調(diào)整原內(nèi)存空間大小的基礎(chǔ)上,還會將原來內(nèi)存中的數(shù)據(jù)移動到 新 的空間。
realloc在調(diào)整內(nèi)存空間的是存在兩種情況:
常見的動態(tài)內(nèi)存錯誤
1. 對NULL指針的解引用操作
調(diào)用 limits.h ,里面定義了最大最小值的極限,如果我們使用malloc函數(shù)開辟一個最大能開辟的空間,很大的可能由于空間大小的原因而開辟失敗,那么就會反還一個空指針給p。
#include<limits.h> void test() {int *p = (int *)malloc(INT_MAX);*p = 20;//如果p的值是NULL,就會有問題free(p); }2. 對動態(tài)開辟空間的越界訪問
調(diào)用 errno.h 可以打印錯誤信息。開辟了10個字節(jié)的空間,而我們卻訪問了0~10一共11個字節(jié)的大小,所以出現(xiàn)了越界訪問。
#include <errno.h> #include<string.h> int main() {char* p = (char*)malloc(10 * sizeof(char));if (p == NULL){printf("%s\n", strerror(errno));return 0;}//使用int i = 0;for(i=0; i<=10; i++){*(p + i) = 'a'+i;}//釋放free(p);p = NULL;return 0; }3. 對非動態(tài)開辟內(nèi)存使用free釋放
這個p是在棧上的,是不能使用free釋放的。
void test() {int a = 10;int *p = &a;free(p);//ok?不ok }4. 使用free釋放一塊動態(tài)開辟內(nèi)存的一部分
使用內(nèi)存之后p的位置已經(jīng)不是原來的位置了。
釋放,只會釋放一部分,但是這個操作不允許!!!
5. 對同一塊動態(tài)內(nèi)存多次釋放
int main() {int* p = (int*)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return 0;}//使用內(nèi)存int i = 0;//1~5for (i = 0; i < 5; i++){*(p+i) = i + 1;}//釋放free(p);p = NULL;free(p);//再次釋放,errreturn 0; }6. 動態(tài)開辟內(nèi)存忘記釋放(內(nèi)存泄漏)
void test() {int* p = (int*)malloc(100);if (NULL != p){*p = 20;} } int main() {test();while (1); }開辟了一定空間之后,程序一直執(zhí)行,但是開辟的空間放在了堆里。如果不釋放,那么其他人也不能使用這個空間。如果程序出現(xiàn)錯誤,開辟的空間會自動被釋放!
經(jīng)典筆試題
題目1
void GetMemory(char* p) {*p = (char*)malloc(100);//確實申請到了100個空間,但是一旦出了這個函數(shù),p就被銷毀 } void Test(void) {char* str = NULL;GetMemory(str);//strcpy(str, "hello world");//當str是NULL指針的時候,會非法訪問內(nèi)存,程序崩潰printf(str);//釋放free(str);str = NULL; } int main() {Test();return 0; }運行結(jié)果,程序崩潰!
接下來分析一下原因。首先進入Test()函數(shù),定義一個char型指針變量str,指向NULL。
GetMemory()函數(shù)中,形參變量p只是str的一個臨時拷貝,然后申請100字節(jié)的空間,用p指針指向頭位置。
走出GetMemory()函數(shù)時,p變量被銷毀,那它開辟的那個空間也就沒有辦法釋放了。
str是空指針的時候,使用strcpy非法訪問,程序崩潰。
正確的改法:
void GetMemory(char** p) GetMemory(&str);題目2
能否打印hello world?
char* GetMemory(void) {char p[] = "hello world";return p; } void Test(void) {char* str = NULL;str = GetMemory();printf(str); } int main() {Test();return 0; }不能
接下來分析一下原因。首先進入Test()函數(shù),定義一個char型指針變量str,指向NULL。
GetMemory()函數(shù)中,定義了一個數(shù)組,數(shù)組存放的內(nèi)容是hello world,數(shù)組名為p。
雖然在p銷毀之前已經(jīng)把p的值返回了,但是數(shù)組的空間卻被回收了,我們已經(jīng)沒有使用權(quán)了。
這個就好比,你在酒店開了一間房,喊朋友3點來玩,結(jié)果你1點的時候退房了,朋友3點來的時候那肯定不能住進這個房間啊。
題目3
int* test() {int a = 10;return &a; } int main() {int *p = test();//printf("heheh\n");printf("%d\n", *p);return 0; }
圖片來自:https://www.jianshu.com/p/c5ad2d7d4b84
運行發(fā)現(xiàn)結(jié)果是對的,你說這巧不巧?
這只能說明開辟的a空間雖然銷毀了,但是里面的值沒有變化,因為沒有人更改。這也是非法訪問的一種,能夠訪問純屬巧合。
如果打印hehe,就把a的函數(shù)棧針覆蓋掉了,得到的結(jié)果就變了。
C/C++程序的內(nèi)存開辟
束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中,效率很高,但是
分配的內(nèi)存容量有限。 棧區(qū)主要存放運行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)、返
回地址等。
配方式類似于鏈表。
柔性數(shù)組
C99 中,結(jié)構(gòu)中的最后一個元素允許是未知大小的數(shù)組,這就叫做『柔性數(shù)組』成員。
使用malloc開辟的大小,前面的sizeof(struct S2) 開辟了四個字節(jié),這是int n的大小,后面的40是給柔性數(shù)組的空間。
struct S2 {int n;int arr[]; };int main() {struct S2* ps = (struct S2*)malloc(sizeof(struct S2) + 40);ps->n = 100;int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}//釋放free(ps);ps = NULL;return 0; }同樣,也可以使用realloc增容
//增容struct S2* ptr = (struct S2*)realloc(ps, sizeof(struct S2)+80);if (ptr == NULL){return 0;}else{ps = ptr;}柔性數(shù)組的好處
第一個好處是:方便內(nèi)存釋放
如果我們的代碼是在一個給別人用的函數(shù)中,你在里面做了二次內(nèi)存分配,并把整個結(jié)構(gòu)體返回給用戶。用戶調(diào)用free可以釋放結(jié)構(gòu)體,但是用戶并不知道這個結(jié)構(gòu)體內(nèi)的成員也需要free,所以你不能指望用戶來發(fā)現(xiàn)這個事。所以,如果我們把結(jié)構(gòu)體的內(nèi)存以及其成員要的內(nèi)存一次性分配好了,并返回給用戶一個結(jié)構(gòu)體指針,用戶做一次free就可以把所有的內(nèi)存也給釋放掉。
第二個好處是:這樣有利于訪問速度
連續(xù)的內(nèi)存有益于提高訪問速度,也有益于減少內(nèi)存碎片。(其實,我個人覺得也沒多高了,反正你跑不了要用做偏移量的加法來尋址)
總結(jié)
以上是生活随笔為你收集整理的C语言第十三课,动态内存分配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西北大学软工专硕专业课面试可能会问到的问
- 下一篇: 自动售卖软件服务器,连锁收银系统中销售商