【轉(zhuǎn)】C 語言程序開發(fā)中的內(nèi)存分配究竟是如何進(jìn)行的渴庆?為什么 calloc() 函數(shù)的效率比 malloc() + memset() 函數(shù)更高铃芦?

引言

在 C 語言程序開發(fā)中,提到動態(tài)內(nèi)存分配時(shí)襟雷,基本上每個(gè)程序員都明白 calloc() 和 malloc() 庫函數(shù)的區(qū)別——calloc() 函數(shù)不僅分配內(nèi)存刃滓,還會將分配后的內(nèi)存清零,而 malloc() 函數(shù)則對分配好的內(nèi)存不做任何操作耸弄。

calloc() 函數(shù)的效率比 malloc() + memset() 函數(shù)更高咧虎?

很多 C 語言程序員常把 calloc() 函數(shù)看作是 malloc() + memset() 函數(shù)的組合。不過计呈,今天我在一個(gè)很偶然的測試中發(fā)現(xiàn) calloc() 函數(shù)和 malloc() + memset() 組合函數(shù)的效率差異還是很大的砰诵。請看:

#include<stdio.h>
#include<stdlib.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
    int i=0;
    char *buf[10];
    while(i<10)
    {
        buf[i] = (char*)calloc(1,BLOCK_SIZE);
        i++;
    }
  return 0征唬;
}

這段 C 語言代碼調(diào)用了 calloc() 函數(shù)分配了一段內(nèi)存,并且重復(fù) 10 次茁彭,編譯并執(zhí)行之(time 命令可以查看 C 語言程序運(yùn)行消耗的時(shí)間)总寒,得到如下結(jié)果:

# gcc t.c
# time ./a.out  
---
real 0m0.287s
user 0m0.095s  
sys  0m0.192s 

現(xiàn)在將 calloc() 函數(shù)改為 malloc() + memset() 函數(shù),修改后的 C 語言代碼如下理肺,請看:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
    int i=0;
    char *buf[10];
    while(i<10)
    {
        buf[i] = (char*)malloc(BLOCK_SIZE);
        memset(buf[i],'\0',BLOCK_SIZE);
        i++;
    }.
    return 0;
}

編譯并執(zhí)行這段 C 語言代碼摄闸,同樣使用 time 命令查看程序運(yùn)行消耗時(shí)間,得到如下結(jié)果妹萨,請看:

# gcc t.c
# time ./a.out  
---
real 0m2.693   
user 0m0.973s  
sys  0m1.721s 

應(yīng)該清楚年枕,這兩段 C 語言代碼的工作是一致的,都是分配一段長度為 BLOCK_SIZE 的內(nèi)存并且清零乎完,但是二者消耗的時(shí)間卻相差非常大熏兄,這就有一個(gè)值得深思的問題:calloc() 函數(shù)做了相同的工作,但是效率卻高得多树姨,這是怎么回事呢霍弹?

?? Tips

弄清楚這一點(diǎn),對于我們以后開發(fā)更高效率的 C 語言程序肯定有所幫助娃弓。

解析

在展開討論之前典格,應(yīng)該明白的是以后如果希望申請一段內(nèi)容為 0 的內(nèi)存,則應(yīng)該使用效率更高的 calloc() 函數(shù)台丛,而不是 malloc() + memset() 函數(shù)的組合耍缴。

因?yàn)?calloc() 函數(shù)在內(nèi)部實(shí)現(xiàn)中,會自行判斷分配后的內(nèi)存是否需要清零挽霉,如果某段分配好的內(nèi)存原本就是零防嗡,那么清零動作就免去了。而 malloc() + memset() 函數(shù)的組合則全額做了“分配 + 清零”的動作侠坎,效率自然是有所差異的蚁趁。

一般來說,C 語言程序員應(yīng)該明白四大點(diǎn):程序实胸,標(biāo)準(zhǔn)庫他嫡,內(nèi)核以及頁表

像 malloc() 和 calloc() 這樣的內(nèi)存分配函數(shù)主要用于分配數(shù)百字 KB 以下的內(nèi)存分配,這樣的分配一般是直接從內(nèi)存池(memory pool)中分配的庐完。當(dāng)內(nèi)存池被用完后钢属,或者某段 C 語言代碼一次性請求分配的內(nèi)存超過剩余內(nèi)存池容量時(shí),malloc() 和 calloc() 將直接向內(nèi)核請求內(nèi)存门躯。

內(nèi)核管理每個(gè)進(jìn)程的實(shí)際 RAM淆党,并確保不同進(jìn)程不會干擾彼此的內(nèi)存,這就是所謂的操作系統(tǒng)“內(nèi)存保護(hù)”機(jī)制。有了這樣的機(jī)制染乌,一個(gè)進(jìn)程的崩潰不會導(dǎo)致其他進(jìn)程跟著崩潰山孔,系統(tǒng)的穩(wěn)定性會得到保障。

因此荷憋,在操作系統(tǒng)內(nèi)核的管理下饱须,當(dāng)某段 C 語言代碼需要使用一段內(nèi)存時(shí),它不能直接使用物理內(nèi)存台谊,而只能通過 mmap() 以及 sbrk() 等系統(tǒng)調(diào)用向內(nèi)核申請蓉媳,由內(nèi)核修改頁表為每個(gè)進(jìn)程提供 RAM。

頁表將內(nèi)存地址映射到實(shí)際的物理 RAM锅铅,在 32 位系統(tǒng)上酪呻,進(jìn)程地址(0x00000000 到 0xffffffff)不是實(shí)際的內(nèi)存地址,而是虛擬內(nèi)存地址盐须,處理器將這些地址分為 4KiB 個(gè)頁玩荠,通過頁表,可以將每個(gè)內(nèi)存頁對應(yīng)到不同的物理 RAM 上贼邓。

一些 C 語言程序員認(rèn)為阶冈,calloc() 等內(nèi)存分配函數(shù)是這樣工作的

C 語言程序調(diào)用 calloc() 申請 256KB 內(nèi)存,于是標(biāo)準(zhǔn)庫調(diào)用系統(tǒng)調(diào)用 mmap() 函數(shù)向內(nèi)核申請塑径,內(nèi)核找到 256KB 未被使用的 RAM女坑,并通過修改頁表的方式將其提供給C語言程序,接著標(biāo)準(zhǔn)庫調(diào)用 memset() 函數(shù)將申請到的內(nèi)存清零统舀,然后從 calloc() 函數(shù)將這段內(nèi)存返回匆骗。

之后,當(dāng)這段 C 語言程序退出后誉简,內(nèi)核會回收分配給它的內(nèi)存碉就,以便給其他進(jìn)程使用。

實(shí)際上

上述過程在理論上是可行的闷串,但是實(shí)際上并不會這樣瓮钥。因?yàn)閮?nèi)存總是有限的,內(nèi)核分配給我們的 C 語言程序使用的內(nèi)存可能是之前其他進(jìn)程使用過的烹吵,如果這段內(nèi)存里有密碼碉熄,密鑰,等其他敏感信息呢年叮?

為了避免出現(xiàn)上述安全隱患具被,內(nèi)核總是在將內(nèi)存交給進(jìn)程之前將其清理掉。當(dāng)然了只损,我們也可以自己調(diào)用清零函數(shù)將使用過的內(nèi)存清零,但是不管如何,mmap() 函數(shù)保證其返回的新內(nèi)存是清零后的總是安全的選擇跃惫。

有一些 C 語言程序可能很早就向內(nèi)核申請了一段內(nèi)存叮叹,但是卻不會立刻使用它,甚至可能根本不會使用它爆存。因此在設(shè)計(jì)操作系統(tǒng)內(nèi)核時(shí)蛉顽,為了效率的最大化,可能內(nèi)核在收到內(nèi)存分配請求時(shí)先较,根本不修改頁表携冤,也不向我們的程序提供任何實(shí)際的 RAM。

內(nèi)核可能僅會將一些地址空間標(biāo)記給我們的程序使用闲勺,但是卻不做實(shí)際的分配工作曾棕。這樣就避免了“分配了內(nèi)存,卻沒被使用”帶來的不必要的開銷了菜循。當(dāng)然翘地,一旦 C 語言程序需要讀寫這些地址空間,就會觸發(fā)一個(gè)缺頁異常癌幕,內(nèi)核再將 RAN 真正的分配給這些地址衙耕,并恢復(fù)程序運(yùn)行。

?? Tips

簡而言之勺远,內(nèi)核為了避免不必要的開銷橙喘,實(shí)際的內(nèi)存分配只有在確保真的有 C 語言代碼使用時(shí)(有寫入動作時(shí))才會進(jìn)行。

也有些 C 語言程序分配內(nèi)存后胶逢,可能(不做任何修改)直接就去讀這些內(nèi)存渴杆,這時(shí),內(nèi)核甚至?xí)屵@些 C 語言程序申請的內(nèi)存指向同一個(gè) 4KiB 頁表宪塔,因?yàn)?mmap() 返回的零填充內(nèi)存都一樣磁奖。如果某個(gè)C語言程序嘗試對申請到的內(nèi)存執(zhí)行寫入操作,那么將觸發(fā)另一種缺頁異常某筐,內(nèi)核將為該 C 語言程序分配一個(gè)新的內(nèi)存頁使用比搭,該內(nèi)存頁不與其他任何進(jìn)程共享。

在 C 語言程序開發(fā)中南誊,一次內(nèi)存分配的實(shí)際過程是這樣的

C 語言程序調(diào)用 calloc() 申請 256KB 內(nèi)存身诺,于是標(biāo)準(zhǔn)庫調(diào)用系統(tǒng)調(diào)用 mmap() 函數(shù)向內(nèi)核申請,內(nèi)核找到 256KB 未被使用的地址空間抄囚,記下該地址空閑現(xiàn)在用于什么霉赡,然后返回。

現(xiàn)在標(biāo)準(zhǔn)庫知道 mmap() 返回的結(jié)果總是用零填充幔托,所以它不需要寫入內(nèi)存穴亏,因此不會出現(xiàn)缺頁異常蜂挪,內(nèi)核不必直接實(shí)際分配內(nèi)存。

最后 C 語言程序退出嗓化,內(nèi)核不需要回收內(nèi)存棠涮,因?yàn)閮?nèi)核根本就沒有分配過內(nèi)存。這樣的效率顯然很高刺覆。

如果使用 memset() 將頁面清零严肪,那么 memset() 的寫入動作將觸發(fā)缺頁異常,內(nèi)核將不得不執(zhí)行分配動作谦屑,并執(zhí)行寫入零動作驳糯。這是一項(xiàng)巨大的工作,這也解釋了為什么 calloc() 比 malloc() + memset() 快的原因氢橙。

現(xiàn)在知道原理了酝枢,我們就可以預(yù)言:如果最后使用了庫函數(shù)分配的內(nèi)存,那么 calloc() 函數(shù)可能仍然比 malloc() + memset() 快充蓝,但是二者之前的區(qū)別將不會再那么大隧枫。

應(yīng)該明白

并非所有的操作系統(tǒng)內(nèi)核都具有分頁虛擬內(nèi)存,因此并非在所有平臺上編譯 C 語言代碼都會得到相同的結(jié)果谓苟。calloc() 函數(shù)可能并不從內(nèi)核申請內(nèi)存官脓,而是從共享內(nèi)存池里申請,而共享內(nèi)存池中可能存儲了上一次被使用時(shí)殘留的垃圾數(shù)據(jù)涝焙,calloc() 可以獲取到這些內(nèi)存卑笨,并且調(diào)用 memset() 將其清零。

不同的操作系統(tǒng)管理內(nèi)存很可能是不一樣的仑撞,有些操作系統(tǒng)內(nèi)核會在空閑時(shí)將內(nèi)存歸零赤兴,已備以后需要獲得歸零內(nèi)存時(shí)使用,而有些則不會隧哮,例如 Linux 就不會提前將內(nèi)存清零桶良。


參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者沮翔。
  • 序言:七十年代末陨帆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子采蚀,更是在濱河造成了極大的恐慌疲牵,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆鼠,死亡現(xiàn)場離奇詭異纲爸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)妆够,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門识啦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來负蚊,“玉大人,你說我怎么就攤上這事袁滥「乔牛” “怎么了灾螃?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵题翻,是天一觀的道長。 經(jīng)常有香客問我腰鬼,道長嵌赠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任熄赡,我火速辦了婚禮姜挺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彼硫。我一直安慰自己炊豪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布拧篮。 她就那樣靜靜地躺著词渤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪串绩。 梳的紋絲不亂的頭發(fā)上缺虐,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音礁凡,去河邊找鬼高氮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛顷牌,可吹牛的內(nèi)容都是我干的剪芍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼窟蓝,長吁一口氣:“原來是場噩夢啊……” “哼罪裹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疗锐,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤坊谁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滑臊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體口芍,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年雇卷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鬓椭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠猴。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖小染,靈堂內(nèi)的尸體忽然破棺而出翘瓮,到底是詐尸還是另有隱情,我是刑警寧澤裤翩,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布资盅,位于F島的核電站,受9級特大地震影響踊赠,放射性物質(zhì)發(fā)生泄漏呵扛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一筐带、第九天 我趴在偏房一處隱蔽的房頂上張望今穿。 院中可真熱鬧,春花似錦伦籍、人聲如沸蓝晒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芝薇。三九已至,卻和暖如春富蓄,著一層夾襖步出監(jiān)牢的瞬間剩燥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工立倍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灭红,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓口注,卻偏偏與公主長得像变擒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子寝志,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容