C語言的malloc

為什么C語言要有malloc

malloc就是memory allocate動(dòng)態(tài)分配內(nèi)存,malloc的出現(xiàn)時(shí)為了彌補(bǔ)靜態(tài)內(nèi)存分配的缺點(diǎn),靜態(tài)分配內(nèi)存有如下缺點(diǎn):

1、比如說,傳統(tǒng)的一維數(shù)組,如int a[5],使用傳統(tǒng)的一維數(shù)組需要事先指定數(shù)組的長(zhǎng)度,而且數(shù)組的長(zhǎng)度必須是一個(gè)常量(宏定義的 常量)

2、傳統(tǒng)數(shù)組(靜態(tài)分配),不能手動(dòng)釋放,只能等待系統(tǒng)釋放,靜態(tài)分配的變量在該函數(shù)內(nèi)運(yùn)行的時(shí)候有效,當(dāng)靜態(tài)分配的變量所在函數(shù)運(yùn)行完之后,該內(nèi)存會(huì)自動(dòng)釋放。靜態(tài)分配的內(nèi)存,是在棧中分配的,其實(shí)在C語言中的函數(shù)調(diào)用也是通過棧來實(shí)現(xiàn)的,棧這種數(shù)據(jù)結(jié)構(gòu)的一個(gè)特點(diǎn)就是(先進(jìn)后出),所以,在調(diào)用函數(shù)的時(shí)候,都是先壓入棧中,然后,再從最上面的函數(shù)開始執(zhí)行,最后,執(zhí)行到main函數(shù)結(jié)束。動(dòng)態(tài)分配通過malloc分配,是在堆中分配的,堆不是一種數(shù)據(jù)結(jié)構(gòu),它是一種排序方式,堆排序。

3、傳統(tǒng)數(shù)組的長(zhǎng)度一旦定義之后,就不能更改,比如說,如果我有一個(gè)業(yè)務(wù)在這之前給分配的大小為100,但是,我現(xiàn)在由于業(yè)務(wù)數(shù)量的增長(zhǎng),原來的大小就無法滿足。

4、靜態(tài)分配不能跨函數(shù)調(diào)用,就是無法在另一個(gè)函數(shù)中,來管理一個(gè)函數(shù)中的內(nèi)存。靜態(tài)分配,只在當(dāng)前函數(shù)有效,當(dāng),靜態(tài)分配所在的函數(shù)運(yùn)行完之后,該變量就不能被其他的函數(shù)所調(diào)用。

malloc是什么

malloc其實(shí)就是一個(gè)可以動(dòng)態(tài)分配內(nèi)存的函數(shù),從而可以很好的彌補(bǔ)上面靜態(tài)分配的缺點(diǎn)。

malloc怎么用

1、使用malloc函數(shù)的時(shí)候,需要包含一個(gè)頭文件#include <malloc.h>

2、malloc函數(shù)只接受一個(gè)形參如,int *p = (int )malloc(sizeof(int)).先來解釋下這句話的含義,int p代表一個(gè)以int類型地址為內(nèi)容的指針變量,p這個(gè)變量占4個(gè)字節(jié)(某些計(jì)算機(jī)),這個(gè)p變量是靜態(tài)分配的一個(gè)變量。

在某些計(jì)算機(jī)的前提下,指針變量所占的大小都是一樣的,無論是char* 還是long *,因?yàn)?,這些指針變量里面存放的是一個(gè)8位16進(jìn)制的地址,所以占四個(gè)字節(jié),當(dāng)然這些都是在某些計(jì)算機(jī)的前提下,并不是所有的都是這樣的。

說道地址的話,就和計(jì)算機(jī)的地址總線有關(guān),如果計(jì)算機(jī)的地址總線是32根,每根地址總線只有兩種狀態(tài)(1或0),32根地址線的話,如果全為1的話,剛好就是一個(gè)8位十六進(jìn)制,一位十六進(jìn)制等于四個(gè)二進(jìn)制(2^4=16)。32根地址總線可以 表示210*2102^102^2種狀態(tài),可以表示的最大內(nèi)存為4G,也就是說32根地址總線(也就是四個(gè)字節(jié) 的指針變量)最大可以表示4G內(nèi)存。

malloc函數(shù)會(huì)返回開辟空間的首地址,加(int *)的目的是讓計(jì)算機(jī)知道,如何去劃分這個(gè)開辟的空間,因?yàn)閏har、int 、long這些類型的字節(jié)大小是不一樣的,我們知道了首地址,還要知道是以幾個(gè)字節(jié)為單元。所以,這句話一共開辟了8個(gè)字節(jié)(某些計(jì)算機(jī)上),這也是為什么我寫sizeof(int),而不是直接寫4的原因。

3、malloc開辟空間所返回的首地址是動(dòng)態(tài)分配的。

malloc使用需要注意的地方

1、申請(qǐng)了內(nèi)存空間后,必須檢查是否分配成功。

2、當(dāng)不需要再使用申請(qǐng)的內(nèi)存時(shí),記得釋放;釋放后應(yīng)該把指向這塊內(nèi)存的指針指向NULL,防止程序后面不小心使用了它。

3、這兩個(gè)函數(shù)應(yīng)該是配對(duì)。如果申請(qǐng)后不釋放就是內(nèi)存泄露;如果無故釋放那就是什么也沒有做。釋放只能一次,如果釋放兩次及兩次以上會(huì)出現(xiàn)錯(cuò)誤(釋放空指針例外,釋放空指針其實(shí)也等于啥也沒做,所以釋放空指針釋放多少次都沒有問題)。

4、雖然malloc()函數(shù)的類型是(void *),任何類型的指針都可以轉(zhuǎn)換成(void *),但是最好還是在前面進(jìn)行強(qiáng)制類型轉(zhuǎn)換,因?yàn)檫@樣可以躲過一些編譯器的檢查。

malloc從哪里得來了內(nèi)存空間

1、malloc()到底從哪里得到了內(nèi)存空間?答案是從堆里面獲得空間。也就是說函數(shù)返回的指針是指向堆里面的一塊內(nèi)存。操作系統(tǒng)中有一個(gè)記錄空閑內(nèi)存地址的鏈表。當(dāng)操作系統(tǒng)收到程序的申請(qǐng)時(shí),就會(huì)遍歷該鏈表,然后就尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后就將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序。

2、什么是堆:堆是大家共有的空間,分全局堆和局部堆。全局堆就是所有沒有分配的空間,局部堆就是用戶分配的空間。堆在操作系統(tǒng)對(duì)進(jìn)程 初始化的時(shí)候分配,運(yùn)行過程中也可以向系統(tǒng)要額外的堆,但是記得用完了要還給操作系統(tǒng),要不然就是內(nèi)存泄漏。

什么是棧:棧是線程獨(dú)有的,保存其運(yùn)行狀態(tài)和局部自動(dòng)變量的。棧在線程開始的時(shí)候初始化,每個(gè)線程的?;ハ嗒?dú)立。每個(gè)函數(shù)都有自己的棧,棧被用來在函數(shù)之間傳遞參數(shù)。操作系統(tǒng)在切換線程的時(shí)候會(huì)自動(dòng)的切換棧,就是切換SS/ESP寄存器。棧空間不需要在高級(jí)語言里面顯式的分配和釋放。

通過上面對(duì)概念的描述,可以知道:

棧是由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、局部變量的值等。操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。堆一般由程序員分配釋放,若不釋放,程序結(jié)束時(shí)可能由OS回收。注意這里說是可能,并非一定。所以我想再強(qiáng)調(diào)一次,記得要釋放!
舉個(gè)例子,如果你在函數(shù)上面定義了一個(gè)指針變量,然后在這個(gè)函數(shù)里申請(qǐng)了一塊內(nèi)存讓指針指向它。實(shí)際上,這個(gè)指針的地址是在棧上,但是它所指向的內(nèi)容卻是在堆上面的!千萬不要認(rèn)為函數(shù)返回,函數(shù)所在的棧被銷毀指針也跟著銷毀,申請(qǐng)的內(nèi)存也就一樣跟著銷毀了!這絕對(duì)是錯(cuò)誤的!因?yàn)樯暾?qǐng)的內(nèi)存在堆上,而函數(shù)所在的棧被銷毀跟堆完全沒有啥關(guān)系。

free()到底釋放了什么

free()釋放的是指針指向的內(nèi)存,不是指針。指針是一個(gè)變量,只有程序結(jié)束時(shí)才被銷毀。釋放了內(nèi)存空間后,原來指向這塊空間的指針還是存在。
野指針(wild pointer):
野指針指的是還沒有初始化的指針。嚴(yán)格地說,編程語言中每個(gè)指針在初始化前都是野指針。一般于未初始化時(shí)便使用指針就會(huì)產(chǎn)生問題。大多數(shù)的編譯器都能檢測(cè)到這一問題并警告用戶。
懸空指針(dangling pointer):
當(dāng)所指向的對(duì)象被釋放或者收回,但是對(duì)該指針沒有作任何的修改,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地址,此情況下該指針便稱懸空指針。若操作系統(tǒng)將這部分已經(jīng)釋放的內(nèi)存重新分配給另外一個(gè)進(jìn)程,而原來的程序重新引用現(xiàn)在的懸空指針,則將產(chǎn)生無法預(yù)料的后果。因?yàn)榇藭r(shí)懸空指針?biāo)赶虻膬?nèi)存現(xiàn)在包含的已經(jīng)完全是不同的數(shù)據(jù)。通常來說,若原來的程序繼續(xù)往懸空指針?biāo)赶虻膬?nèi)存地址寫入數(shù)據(jù),這些和原來程序不相關(guān)的數(shù)據(jù)將被損壞,進(jìn)而導(dǎo)致不可預(yù)料的程序錯(cuò)誤。這種類型的程序錯(cuò)誤,不容易找到問題的原因,通常會(huì)導(dǎo)致段錯(cuò)誤(Linux系統(tǒng)中)和一般保護(hù)錯(cuò)誤(Windows系統(tǒng)中)。如果操作系統(tǒng)的內(nèi)存分配器將已經(jīng)被覆蓋的數(shù)據(jù)區(qū)域再分配,就可能會(huì)影響系統(tǒng)的穩(wěn)定性。

無論是野指針還是懸空指針,都是指向無效內(nèi)存區(qū)域(這里的無效指的是"不安全不可控")的指針。 訪問"不安全可控"(invalid)的內(nèi)存區(qū)域?qū)?dǎo)致"Undefined Behavior"。也就是說:任何可能都會(huì)發(fā)生。要么編譯失敗,要么執(zhí)行得不正確(崩潰(e.g. segmentation fault)或者悄無聲息地產(chǎn)生不正確的執(zhí)行結(jié)果),或者偶爾會(huì)正確地產(chǎn)生程序員希望運(yùn)行的結(jié)果。

如何避免使用野指針和懸空指針

對(duì)于野指針:養(yǎng)成在定義指針后且在使用之前完成初始化的習(xí)慣就好。
對(duì)于懸空指針:一個(gè)避免這個(gè)錯(cuò)誤的方法是在釋放它的引用后將該指針的值重置為NULL。

在子函數(shù)中調(diào)用malloc申請(qǐng)內(nèi)存的方法

方法一:函數(shù)返回
將malloc得到的內(nèi)存首地址通過函數(shù)的返回值返回到主函數(shù)。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
char* test()
{
    char *p;
    p = (char*)malloc(10 * sizeof(char));
    strcpy(p, "123456789" );
    return p;
}
void main()
{
    char *str = NULL ;
    str = test();
    printf("%s\n", str);
    free(str);
}

方法二:二級(jí)指針
將malloc得到的內(nèi)存首地址通過二級(jí)指針返回到主函數(shù)。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
void test(char **p)
{
    *p = (char*)malloc(10 * sizeof(char));
    strcpy(*p, "123456789" );   
}
void main()
{
    char *str = NULL ;
    test(&str);
    printf("%s\n", str);
    free(str);
}

常見誤區(qū):
錯(cuò)誤一:使用一級(jí)指針

#include <stdio.h>
#include <malloc.h>
#include <string.h>
void test(char *p) 
{
    p = (char*)malloc(10 * sizeof(char));
    strcpy(*p, "123456789" );   
}
void main()
{
    char *str = NULL ;
    test(str);
    printf("%s\n", str);
    free(str);
}

看上去合情合理,把malloc得的地址賦給指針p,這樣我們傳入的str就指向申請(qǐng)的內(nèi)存了。但事實(shí)是,str的值并沒有變化。我們可以先看下方的代碼。

#include <stdio.h>
void test(char c)
{
    c = 'B';
}
void main()
{
    char ch = 'A' ;
    test(ch);
    printf("%c\n", ch);
}

調(diào)用test()后,主函數(shù)里面的ch值還是’A’,而不是’B’。這是因?yàn)樵谡{(diào)用函數(shù)的時(shí)候,char c 事實(shí)上是被復(fù)制進(jìn)函數(shù)內(nèi)部的,函數(shù)內(nèi)的操作不會(huì)影響到原值。 指針也是一樣的道理。傳入一個(gè)一級(jí)指針,只能修改它指向的數(shù)據(jù),而不能修改它指向的地址。所以我們應(yīng)該傳入一個(gè)二級(jí)指針,這個(gè)指針指向一級(jí)指針。這樣我們就能修改位于二級(jí)指針指向的數(shù)據(jù),即一級(jí)指針指向的地址了。
錯(cuò)誤二:二級(jí)指針未指向存在的一級(jí)指針

#include <stdio.h>
#include <malloc.h>
#include <string.h>
void test(char **p)
{
    *p = (char*)malloc(10 * sizeof(char));
    strcpy(*p, "123456789" );   
}
void main()
{
    char **str = NULL ; //原代碼:char *str = NULL;
    test(str);          //       test(&str);
    printf("%s\n", str);
    free(str);
}

為什么我使用了二級(jí)指針,仍然是錯(cuò)誤的呢?對(duì)比下正確的代碼,就一目了然了。正確代碼中,通過對(duì)一級(jí)指針str進(jìn)行取址,得到指向str的二級(jí)指針,在子函數(shù)中就可以操作str的值了。而錯(cuò)誤代碼中,二級(jí)指針的值為NULL,這樣的話,子函數(shù)中操作的是地址為NULL的內(nèi)存,這當(dāng)然是不對(duì)的。

以上內(nèi)容整理于:
C語言指針之二malloc的用法及詳解
C語言中 malloc函數(shù)用法
C語言在子函數(shù)中調(diào)用malloc申請(qǐng)內(nèi)存的方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容