(C語言內存十六)C語言動態內存分配


靜態內存分配

在進程的地址空間中,代碼區、常量區、全局數據區的內存在程序啟動時就已經分配好了,它們大小固定,不能由程序員分配和釋放,只能等到程序運行結束由操作系統回收。這稱為靜態內存分配。

動態內存分配

棧區和堆區的內存在程序運行期間可以根據實際需求來分配和釋放,不用在程序剛啟動時就備足所有內存。這稱為動態內存分配。

使用靜態內存的優點是速度快,省去了向操作系統申請內存的時間,缺點就是不靈活,缺乏表現力,例如不能控制數據的作用范圍,不能使用較大的內存。而使用動態內存可以讓程序對內存的管理更加靈活和高效,需要內存就立即分配,而且需要多少就分配多少,從幾個字節到幾個GB不等;不需要時就立即回收,再分配給其他程序使用。

棧和堆的區別

棧區和堆區的管理模式有所不同:棧區內存由系統分配和釋放,不受程序員控制;堆區內存完全由程序員掌控,想分配多少就分配多少,想什么時候釋放就什么時候釋放,非常靈活。

程序啟動時會為棧區分配一塊大小適當的內存,對於一般的函數調用這已經足夠了,函數進棧出棧只是 ebp、esp 寄存器指向的變換,或者是向已有的內存中寫入數據,不涉及內存的分配和釋放。當函數中有較大的局部數組時,比如 1024*10 個元素,編譯器就會在函數代碼中插入針對棧的動態內存分配函數,這樣函數被調用時才分配內存,不調用就不分配。

我們經常聽說“棧內存的分配效率要高於堆”就是這個道理,因為大部分情況下並沒有真的分配棧內存,僅僅是對已有內存的操作。

動態內存分配函數

堆(Heap)是唯一由程序員控制的內存區域,我們常說的動態內存分配也是在這個區域。在堆上分配和釋放內存需要用到C語言標准庫中的幾個函數:malloc()、calloc()、realloc() 和 free()。

這幾個函數的具體用法在C標准庫中已經進行了講解(點擊上面鏈接查看),這里不再贅述,僅作簡單的對比,並給出一個綜合示例。

1) malloc()

原型:void* malloc (size_t size);

作用:在堆區分配 size 字節的內存空間。

返回值:成功返回分配的內存地址,失敗則返回NULL。

注意:分配內存在動態存儲區(堆區),手動分配,手動釋放,申請時空間可能有也可能沒有,需要自行判斷,由於返回的是void*,建議手動強制類型轉換。

2) calloc()

原型:void* calloc(size_t n, size_t size);

功能:在堆區分配 n*size 字節的連續空間。

返回值:成功返回分配的內存地址,失敗則返回NULL。

注意:calloc() 函數是對 malloc() 函數的簡單封裝,參數不同,使用時務必小心,第一參數是第二參數的單元個數,第二參數是單位的字節數。

3) realloc()

原型:void* realloc(void *ptr, size_t size);

功能:對 ptr 指向的內存重新分配 size 大小的空間,size 可比原來的大或者小,還可以不變(如果你無聊的話)。

返回值:成功返回更改后的內存地址,失敗則返回NULL。

4) free()

原型:void free(void* ptr);

功能:釋放由 malloc()、calloc()、realloc() 申請的內存空間。

幾點注意

  1. 每個內存分配函數必須有相應的 free 函數,釋放后不能再次使用被釋放的內存。

  2. 在分配內存時最好不要直接用數字指定內存空間的大小,這樣不利於程序的移植。因為在不同的操作系統中,同一數據類型的長度可能不一樣。為了解決這個問題,C語言提供了一個判斷數據類型長度的操作符,就是 sizeof。

  3. free(p) 並不能改變指針 p 的值,p 依然指向以前的內存,為了防止再次使用該內存,建議將 p 的值手動置為 NULL。

sizeof 是一個單目操作符,不是函數,用以獲取數據類型的長度時必須加括號,例如 sizeof(int)、sizeof(char) 等。

實例

最后是一個綜合的示例:

#include <stdio.h>
#include <stdlib.h>
#define  N  (5)
#define  N1 (7)
#define  N2 (3)
int main()
{
    int *ip;
    int *large_ip;
    int *small_ip;
    if((ip = (int*)malloc(N * sizeof(int))) == NULL)
    {
        printf("memory allocated failed!\n");
        exit(1);
    }
    int i;
    for(i = 0; i < N; i++)
    {
        ip[i] = i;
        printf("ip[%d] = %d\t", i, ip[i]);
    }
    printf("\n");
    if((large_ip = (int* )realloc(ip, N1 * sizeof(int))) == NULL)
    {
        printf("memory allocated failed!\n");
        exit(1);
    }
    for(i = N; i < N1; i++)
        large_ip[i] = 9;
    for(i = 0; i < N1; i++)
        printf("large_ip[%d] = %d\t", i, large_ip[i]);
    printf("\n");
    if((small_ip = (int*)realloc(large_ip, N2 * sizeof(int))) == NULL)
    {
        printf("memory allocated failed!\n");
        exit(1);
    }
    for(i = 0; i < N2; i++)
        printf("small_ip[%d] = %d\t", i, small_ip[i]);
    printf("\n");
    free(small_ip);
    small_ip = NULL;
    system("pause");
    return 0;
}

運行結果:

ip[0] = 0    ip[1] = 1    ip[2] = 2    ip[3] = 3    ip[4] = 4
large_ip[0] = 0    large_ip[1] = 1    large_ip[2] = 2    large_ip[3] = 3    large_ip[4] = 4    large_ip[5] = 9    large_ip[6] = 9
small_ip[0] = 0    small_ip[1] = 1    small_ip[2] = 2

代碼說明:

  1. 代碼看似很長,其實較為簡單,首先分配一個包含5個整型的內存區域,分別賦值0到4;再用realloc函數擴大內存區域以容納7個整型數,對額外的兩個整數賦值為9;最后再用realloc函數縮小內存區域,直接輸出結果(因為realloc函數會自動復制數據)。

  2. 這次把分配函數與驗證返回值驗證寫在了一起,為的是書寫方便,考慮到優先級問題添加了適當的括號,這種寫法較為常用,注意學習使用。

  3. 本例free函數只用釋放small_ip指針即可,如函數介紹中注意里提到的,另外兩個指針已被系統回收,不能再次使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM