當前位置:
首頁 > 最新 > 《C語言高性能編程》樣章-申請內存

《C語言高性能編程》樣章-申請內存

在正式項目中使用C語言的第一課應該就是學會怎麼申請內存。因為如果你不動態申請內存,那將根本無法去開發任何可用的實際項目(PS:軍工程序除外)!

C語言的內存申請和平時其它常用語言的方式不太一樣。比如在JAVA或者C#中,一般的申請內存(JAVA/C#叫申請對象)使用new關鍵字來完成。比如有個class People,需要聲明並且初始化一個People的對象,通常使用語句:

但是C語言並不存在new關鍵字,而是使用alloc內存分配函數簇來解決問題。alloc函數簇常用的一共有3個函數,分別是:

void *malloc(size_t size);

void *calloc(size_t numbs,size_t size);

void *realloc(void *ptr, size_t size);

首先明確一下這3個內存分配函數的一個共同特點:內存對齊。不管這3個函數,使用哪一個函數來申請內存,全部都會使用到內存對齊。比如malloc,它只是申請一塊有參數size指定大小的內存,如果size不按照AlignSize(AlignSize根據操作系統的位數來決定,一般32位系統為4,64位操作系統為8)對齊,也就是說size % AlignSize != 0,那麼根據內存對齊原則,size的大小實際上會自動擴大到AlignSize的最小整數倍。比如size為25,我們的系統是64位操作系統,AlignSize為8,所以本意上我們需要申請的內存大小為25,但是系統實際給我們申請的大小是32。對此,專門有個公式可以換算:

這個公式也可以定義為一個宏,加在自己的項目中使用。那麼同理對於realloc來說,也是對於參數的size來對齊。比較特殊的是calloc函數,因為calloc函數一般用來申請幾個連接在一起的單位內存塊。它的參數有2個,一個是代表個數的numbs,另外一個是代表申請單位內存大小的size。因為內存對齊是一個單位一個單位對齊的,所以對於calloc函數來說,內存對齊是對於第二個參數的size來確定的,然後再乘以nums。比如我們需要在64位系統上申請nums=2,size=25的內存塊,使用calloc最後系統分配的大小是64,而不是56.

注意:內存對齊其實是操作系統的一個特性,並不是C語言的特性。所有的程序都是跑在操作系統上的,相應的所有的程序都是要遵循這套規則的。不僅僅是C,別的語言比如JAVA、C#、Python等也會同樣遵循這套規則。

第二個共同點是返回值:

當申請內存成功的時候,即返回可用內存;

當申請內存失敗的時候,都返回NULL指針,並且設置errno。

闡述完共同點,講講他們不同的地方:

calloc函數。先來看看這個函數。calloc相比於malloc,它除了申請內存之外還做了一件事情:給申請的內存清零.也就是說,從最後的效果看,約等於執行了這樣的代碼:

清零的好處是你拿到的內存的已經是乾淨的了,而不需要你再手動的去執行一次內存清理工作,特別是申請的內存需要存放二進位數據或者是字元串類型數據的時候,清零的內存相比不清零的內存更靠譜,也更少的發生bug;

malloc函數,它只是用來申請內存,但並不把申請的內存清零.這個點很重要。特別如上面提到的:如果當你需要為二進位數據或者是string類型的數據申請內存的時候,使用malloc函數一定要記得清零,否則可能會引起越界錯誤。因為不清零的內存裡面可能存在你剛剛釋放掉的內存,或者直接是亂碼之類的東西,可能會干擾到你程序的正確執行。在現實的項目中確實發生過這類問題。那麼怎麼能看出來是否被清零呢?可以使用gdb調試器的p命令查看變數內容;

realloc函數。顧名思義是重新分配內存的意思。它有兩個參數,一個是ptr指針,這是表示原來的內存指針;另外一個是size,size也就是要修改ptr指針到新的內存大小。realloc函數的功能有很多,會隨著參數的不同而產生不同的效果。先假設原來的ptr指針內存大小為n。

當n > size的時候,截取的內容不會發生什麼變化;

當n < size的時候,新分配內存的那部分(大小為size - n)不會被初始化(PS:注意不會清零);

如果null == ptr為真,realloc相當於執行malloc(PS:不清零);

如果0 == size為真,相當於調用了free釋放內存操作;

如果null != ptr為真,並且重新分配了內存,原來的ptr指針會自動的被調用free釋放掉內存。

在C語言中,因為沒有異常這種特性,而且C語言的內存都是需要程序員自己管理的,所以我們應該對於所有的內存申請操作都要做是否申請成功的判斷,比如:

再重申一遍,用C語言寫程序,所有的操作(PS:特別是內存申請、釋放、網路的調用等等)都需要判斷是否正確執行了。

上例使用了malloc來作為演示,如果調用malloc函數申請內存,最好是在申請內存成功後再使用memset清空一下內存。一般情況下,建議使用calloc函數來申請內存。使用calloc函數來申請內存,上面的程序將會被改變成如下所示:

對於realloc,這裡主要的坑在於我們不能為了簡單方便,輕易的將新分配的內存直接賦值給原來的ptr指針,而是應該先聲明一個臨時性的變數,檢查臨時性變數的正確性後,再將臨時變數指針指向原來的ptr指針。如下所示:

錯誤的用法

這種寫法有問題。如果realloc函數申請內存失敗,該函數會返回NULL,而原來的str內存是不為NULL的,所以這段內存就會丟失指針指向,後續將無法被釋放(我們不能釋放一個為NULL的指針),內存發生了泄露;

正確的用法

這才是正確的寫法,重新聲明了一個臨時指針變數,用來存新申請的內存指針,再判斷內存申請正確性過後,將臨時的指針變數指向原來的指針變數。這樣做就不會出現當新內存申請失敗的時候,原來的內存被賦值為NULL,造成無法free導致內存泄露的問題。

realloc還有一種可能引發bug的問題:再一次釋放內存。示例如下:

這種用法可以稱之為畫蛇添足。realloc在重新申請內存成功的情況下,原來的內存會自動去free釋放掉,所以不需要我們自己再次手動的去調用free釋放。而且上面示例的釋放內存方式也不合乎我們的釋放內存規則。

這些就是在使用C語言申請內存時的函數和注意點,說多也不多說少也不少。申請內存是使用C語言開發實際項目的第一步,必須要完全掌握並且玩轉內存,後面的項目才能開發下去。

公眾號已經開啟留言功能,歡迎大家在技術上和我交流!

如果大家喜歡我的文章,請關注我的微信公眾號!

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 94geek的大嘴 的精彩文章:

TAG:94geek的大嘴 |