1. 程式人生 > >C語言系列(五)記憶體的分配與釋放

C語言系列(五)記憶體的分配與釋放

首先我們來科普一下:

什麼是堆?說到堆,又忍不住說到了棧!什麼是 棧?

1、什麼是堆:堆是大家共有的空間,分全域性堆和區域性堆。全域性堆就是所有沒有分配的空間,區域性堆就是使用者分配的空間。堆在作業系統對程序 初始化的時候分配,執行過程中也可以向系統要額外的堆,但是記得用完了要還給作業系統,要不然就是記憶體洩漏。
2、什麼是棧:棧是執行緒獨有的,儲存其執行狀態和區域性自動變數的。棧線上程開始的時候初始化,每個執行緒的棧互相獨立。每個函式都有自己的棧,棧被用來在函式之間傳遞引數。作業系統在切換執行緒的時候會自動的切換棧,就是切換SS/ESP暫存器。棧空間不需要在高階語言裡面顯式的分配和釋放。

C語言程式編譯的記憶體分配,堆與棧的區別:

  • 棧是由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等。操作方式類似於資料結構中的棧。
    堆一般由程式設計師分配釋放,若不釋放,程式結束時可能由OS回收。注意這裡說是可能,並非一定。再強調一次,記得要釋放!
  • 棧區(stack) :
    //windows下,棧記憶體分配2M(確定的常數),超出了限制,提示stack overflow錯誤
    //編譯器自動分配釋放,主要存放函式的引數值,區域性變數值等;
  • 堆區(heap):程式設計師手動分配釋放,作業系統80%記憶體

  • 全域性區或靜態區:存放全域性變數和靜態變數;程式結束時由系統釋放,分為全域性初始化區和全域性未初始化區;

  • 字元常量區:常量字串放與此,程式結束時由系統釋放;

  • 程式程式碼區:存放函式體的二進位制程式碼。

栗子:

int a=0;        //全域性初始化區
char *p1;       //全域性未初始化區
void main()
{
   int b;          //棧
   char s[]="bb";  //棧
   char *p2;       //棧
   char *p3="123"; //其中,“123\0”常量區,p3在棧區
   static int c=0; //全域性區
   p1=(char*)malloc(10);   //10個位元組區域在堆區
   strcpy(p1,"123");    //"123\0"在常量區,編譯器 可能 會優化為和p3的指向同一塊區域
}

棧記憶體

void stackFun(){
    int a[1024];
    //棧記憶體自動釋放
}

堆記憶體

void heapFun(){
    //40M記憶體
    //位元組
    //void *任意型別的指標
    int* p = malloc(1024 * 1024 * 10 * sizeof(int));

    //釋放
    free(p);
}
void main(){    
    //在堆記憶體上,分配40M的記憶體
    while (1){
        Sleep(1000);    
        stackFun();
    }

    getchar();
}

建立一個數組,動態指定陣列的大小(在程式執行過長中,可以隨意的開闢指定大小的記憶體,以供使用,相當於Java中的集合)
靜態記憶體分配,分配記憶體大小的是固定,問題:1.很容易超出棧記憶體的最大值 2.為了防止記憶體不夠用會開闢更多的記憶體,容易浪費記憶體

動態記憶體分配,在程式執行過程中,動態指定需要使用的記憶體大小,手動釋放,釋放之後這些記憶體還可以被重新使用(活水)

函式:calloc() 分配記憶體空間並初始化
calloc() 函式用來動態地分配記憶體空間並初始化為 0,其原型為:

  void* calloc (size_t num, size_t size);

calloc() 在記憶體中動態地分配 num 個長度為 size 的連續空間,並將每一個位元組都初始化為 0。所以它的結果是分配了 num*size 個位元組長度的記憶體空間,並且每個位元組的值都是0。

【返回值】分配成功返回指向該記憶體的地址,失敗則返回 NULL。

函式:malloc() 動態地分配記憶體空間

malloc() 函式用來動態地分配記憶體空間(如果你不瞭解動態記憶體分配,請檢視:C語言動態記憶體分配及變數儲存類別),其原型為:

void* malloc (size_t size);

應用在程式中程式碼如下:

void main(){
    //靜態記憶體分配建立陣列,陣列的大小是固定的
    //int i = 10;
    //int a[i];

    int len;
    printf("輸入陣列的長度:");
    scanf("%d",&len);

    //開闢記憶體,大小len*4位元組
    int* p = malloc(len * sizeof(int));
    //p是陣列的首地址,p就是陣列的名稱
    //給陣列元素賦值(使用這一塊剛剛開闢出來的記憶體區域)
    int i = 0;
    for (; i < len - 1; i++){
        p[i] = rand() % 100;
        printf("%d,%#x\n", p[i], &p[i]);
    }

    //手動釋放記憶體 
    //free()釋放動態分配的記憶體空間
    free(p);

    getchar();
}

realloc 重新分配記憶體

void main(){
    int len;
    printf("第一次輸入陣列的長度:");
    scanf("%d", &len);

    //int* p = malloc(len * sizeof(int));   
    int* p = calloc(len, sizeof(int));
    int i = 0;
    for (; i < len; i++){
        p[i] = rand() % 100;
        printf("%d,%#x\n", p[i], &p[i]);
    }

    int addLen;
    printf("輸入陣列增加的長度:");
    scanf("%d", &addLen);
    //記憶體不夠用,擴大剛剛分配的記憶體空間
    //1.原來記憶體的指標 2.記憶體擴大之後的總大小        
    int* p2 = realloc(p, sizeof(int) * (len + addLen));
    if (p2 == NULL){
        printf("重新分配失敗,世界那麼大,容不下我。。。");
    }

新分配記憶體的兩種情況:

//縮小,縮小的那一部分資料會丟失
//擴大,(連續的)
1.如果當前記憶體段後面有需要的記憶體空間,直接擴充套件這段記憶體空間,realloc返回原指標
2.如果當前記憶體段後面的空閒位元組不夠,那麼就使用堆中的第一個能夠滿足這一要求的記憶體塊,將目前的資料複製到新的位置,並將原來的資料庫釋放掉,返回新的記憶體地址
3.如果申請失敗,返回NULL,原來的指標仍然有效

//接著上面的程式碼重新賦值
    i = 0;
    printf("--------------------------\n");
    for (; i < len + addLen; i++){
        p2[i] = rand() % 200;
        printf("%d,%#x\n", p2[i], &p2[i]);
    }

    //手動釋放記憶體
    if (p != NULL){
        free(p);
        p = NULL;
    }   
    if (p2 != NULL){
        free(p2);
        p2 = NULL;
    }

    getchar();
}

記憶體分配的幾個注意細節

1.不能多次釋放;
2.釋放完之後(指標仍然有值),給指標置NULL,標誌釋放完成;
3.記憶體洩露(p重新賦值之後,再free,並沒有真正釋放記憶體);

void main(){
    int len;
    printf("輸入陣列的長度:");
    scanf("%d", &len);

    int* p = malloc(len * sizeof(int));     
    int i = 0;
    for (; i < len; i++){
        p[i] = rand() % 100;
        printf("%d,%#x\n", p[i], &p[i]);
    }

    if (p != NULL){
        free(p);
        p = NULL;
    }

    getchar();
}

以上就是C語言中對記憶體的分配與釋放,常用的幾個函式~

更多系列相關文章傳送門: