日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

詳解c語言中的動態內存分配問題

作者:WindFall1314 更新時間: 2022-07-19 編程語言

詳解c語言中的動態內存分配問題

文章目錄

  • **詳解c語言中的動態內存分配問題**
  • **一.為什么存在動態內存分配**
  • **二.動態內存函數的介紹**
    • **2.1 malloc和free**
    • **2.2 calloc**
    • **2.3 realloc**
  • **3. 常見的動態內存錯誤**
    • **3.1 對NULL指針的解引用操作**
    • **3.2 對動態開辟空間的越界訪問**
    • **3.3 對非動態開辟內存使用free釋放**
    • **3.4 使用free釋放一塊動態開辟內存的一部分**
    • **3.5 對同一塊動態內存多次釋放**
    • **3.6 動態開辟內存忘記釋放(內存泄漏)**
  • **4.讓我們來看看它們的“愛恨情仇”**
  • **5. C/C++程序的內存開辟**
  • **6.1 柔性數組的特點**
    • **6.2 柔性數組的使用**
    • **6.3 柔性數組的優勢**

一.為什么存在動態內存分配

我們已經掌握的內存開辟方式有:

int val = 20;//在??臻g上開辟四個字節
char arr[10] = {0};//在??臻g上開辟10個字節的連續空間

但是上述的開辟空間的方式有兩個特點:

  1. 空間開辟大小是固定的。
  2. 數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。
    但是對于空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程序運行的時候才能知道,
    那數組的編譯時開辟空間的方式就不能滿足了。
    這時候就只能試試動態存開辟了。

二.動態內存函數的介紹

2.1 malloc和free

C語言提供了一個動態內存開辟的函數:

void* malloc (size_t size);

這個函數向內存申請一塊連續可用的空間,并返回指向這塊空間的指針。

  • 如果開辟成功,則返回一個指向開辟好空間的指針。
  • 如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查。
  • 返回值的類型是 void ,所以malloc函數并不知道開辟空間的類型,具體在使用的時候使用者自己*
    來決定。
  • 如果參數 size 為0,malloc的行為是標準是未定義的,取決于編譯器。

C語言提供了另外一個函數free,專門是用來做動態內存的釋放和回收的,函數原型如下:

void free (void* ptr);

free函數用來釋放動態開辟的內存。

  • 如果參數 ptr 指向的空間不是動態開辟的,那free函數的行為是未定義的。
  • 如果參數 ptr 是NULL指針,則函數什么事都不做。
  • malloc和free都聲明在 stdlib.h 頭文件中。

看下面代碼:

#include <stdio.h>
int main()
{
    int* ptr = NULL;
    ptr = (int*)malloc(num*sizeof(int));
    if(NULL != ptr)//判斷ptr指針是否為空
    {
    int i = 0;
    for(i=0; i<num; i++)
    {
    *(ptr+i) = 0;
    }
    }
    free(ptr);//釋放ptr所指向的動態內存
    ptr = NULL;
    return 0;
}

2.2 calloc

void* calloc (size_t num, size_t size);

分配和零初始化數組

為元素數組分配一個內存塊,每個元素都長字節,并將其所有位初始化為零。

有效結果是分配零初始化的字節內存塊。

如果為零,則返回值取決于特定的庫實現(它可能是也可能不是空指針),但返回的指針不應被取消引用。

  • 函數的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個字節初始化為0。
  • 與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為全0。
#include<stdio.h>
#include<stdio.h>
int main(){
    int *p =(int*)calloc(10,sizeof(int));
    if(NULL!=p){
        //使用空間
    }
    free(p);
    p=NULL;
    return 0;
}

image-20220718165341845

所以如何我們對申請的內存空間的內容要求初始化,那么可以很方便的使用calloc函數來完成任務。

2.3 realloc

  • realloc函數的出現讓動態內存管理更加靈活。

  • 有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時候內存,我們一定會對內存的大小做靈活的調整。那 realloc 函數就可以做到對動態開辟內存大小的調整。

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

    重新分配內存塊

    更改 所指向的內存塊的大小。

    該函數可以將內存塊移動到新位置(其地址由函數返回)。

    內存塊的內容將保留到新舊大小中較小的一個,即使該塊被移動到新位置也是如此。如果新部分較大,則新分配部分的值不確定。

    如果這是一個空指針,則該函數的行為類似于分配一個新的字節塊并返回指向其開頭的指針(malloc)。

  • ptr 是要調整的內存地址

  • size 調整之后新大小

  • 返回值為調整之后的內存起始位置。

  • 這個函數調整原內存空間大小的基礎上,還會將原來內存中的數據移動到 新 的空間。

  • realloc在調整內存空間的是存在兩種情況:

    • ? 情況一:原有空間之后又足夠大的空間。
    • ? 情況二:原有空間只有沒有足夠大的空間。

    image-20220718170302412

    情況1
    當是情況1 的時候,要擴展內存就直接原有內存之后直接追加空間,原來空間的數據不發生變化。
    情況2
    當是情況2 的時候,原有空間之后沒有足夠多的空間時,擴展的方法是:在堆空間上另找一個合適大小
    的連續空間來使用。這樣函數返回的是一個新的內存地址。
    由于上述的兩種情況,realloc函數的使用就要注意一些。

舉個例子:

#include <stdio.h>
int main()
    {
    int *ptr = (int*)malloc(100);
    if(ptr != NULL)
    {
      //業務處理
    }
    else
    {
      exit(EXIT_FAILURE);  
    }
    //擴展容量
    //代碼1
    ptr = (int*)realloc(ptr, 1000);//這樣可以嗎?(如果申請失敗會如何?)
    //代碼2
    int*p = NULL;
    p = realloc(ptr, 1000);
    if(p != NULL)
    {
    ptr = p;
    }
    //業務處理
    free(ptr);
    return 0;
}

在代碼1的情況下,有可能返回的為空指針,程序繼續往后運行會有不可預料的錯誤。

需要設置一個臨時的指針變量來存儲開辟空間的起始地址。并且需要判斷是不是空指針,不是空指針,我們才能大膽將它托付給ptr。

3. 常見的動態內存錯誤

3.1 對NULL指針的解引用操作

void test()
{
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;//如果p的值是NULL,就會有問題
    free(p);
}

3.2 對動態開辟空間的越界訪問

void test()
{
    int i = 0;
    int *p = (int *)malloc(10*sizeof(int));
    if(NULL == p)
    {
    exit(EXIT_FAILURE);
    }
    for(i=0; i<=10; i++)
    {
    *(p+i) = i;//當i是10的時候越界訪問
    }
    free(p);
}

3.3 對非動態開辟內存使用free釋放

void test()
{
    int a = 10;
    int *p = &a;
    free(p);//ok?
}

3.4 使用free釋放一塊動態開辟內存的一部分

void test()
{
    int *p = (int *)malloc(100);
    p++;
    free(p);//p不再指向動態內存的起始位置
}

3.5 對同一塊動態內存多次釋放

void test()
{
    int *p = (int *)malloc(100);
    free(p);
    free(p);//重復釋放
}

3.6 動態開辟內存忘記釋放(內存泄漏)

void test()
{
    int *p = (int *)malloc(100);
    if(NULL != p)
    {
    *p = 20;
    }
}
int main()
{
    test();
    while(1);
}

忘記釋放不再使用的動態開辟的空間會造成內存泄漏。
切記:
動態開辟的空間一定要釋放,并且正確釋放 。

4.讓我們來看看它們的“愛恨情仇”

4.1 題目1:

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}

test()執行后,會是什么了?

淺談一下,str是一個指針變量,初始為NULL;調用GetMemory(),在GetMemory()這個函數的棧幀內創建了一個臨時指針變量p,初始值為NULL。但是p不甘心啊,我要malloc一下,世界那么大,它也想去看看!一下就看到了堆區的一為姑娘(體重100個字節),正當它打算要入贅當上門女婿的時候,突然家里地震了(GetMemory()),那叫一個慘不忍睹,政府直接把這塊地回收改名了,這下p傻眼了,我的戶口本沒了,它成黑人了,老丈人也不同意這門親事了,從此他抑郁而終,但是那位重100字節的姑娘心里一直有他(堆區開辟的空間一直在),但是她的心意也沒人懂(沒人能找到這塊空間),也郁郁寡歡下去(內存泄漏)。然而這一切的一切,和str毛線關系沒有.所以程序是會崩掉的 。

4.2 題目2

char *GetMemory()
{
    char p[] = "hello world";
    return p;
}
void Test()
{
    char *str = NULL;
    str = GetMemory();
    printf(str);
}

這個代碼你們看到后想到了什么樣的愛情故事了?我來分享一下我的想法,前面的情節和第一個差不多,經歷了同樣的事情后,p在彌留之際,選擇了把那位姑娘的地址飛鴿傳書給了它的兄弟str,str也是個精神小伙,很快找到了她的地址,但是找到后發現這已經不是它兄弟所說的姑娘了,一切都變了。。。如果寫成char p= char p[] = “hello world”;p就是一個常量字符串的地址,不會隨著函數棧幀的消失而被歸還給操作系統。*

5. C/C++程序的內存開辟

image-20220718183931868

  1. 棧區(stack):在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結
    束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是
    分配的內存容量有限。 棧區主要存放運行函數而分配的局部變量、函數參數、返回數據、返
    回地址等。
  2. 堆區(heap):一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收。分配方式類似于鏈表。
  3. 數據段(靜態區)(static)存放全局變量、靜態數據。程序結束后由系統釋放。
  4. 代碼段:存放函數體(類成員函數和全局函數)的二進制代碼。

6.1 柔性數組的特點

  • 結構中的柔性數組成員前面必須至少一個其他成員。
  • sizeof 返回的這種結構大小不包括柔性數組的內存。
  • 包含柔性數組成員的結構用malloc ()函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。
typedef struct st_type
{
    int i;
    int a[0];//柔性數組成員
}type_a;
printf("%d\n", sizeof(type_a));//輸出的是4

6.2 柔性數組的使用

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//業務處理
p->i = 100;
for(i=0; i<100; i++)
{
	p->a[i] = i;
}
free(p);

這樣柔性數組成員a,相當于獲得了100個整型元素的連續空間

6.3 柔性數組的優勢

上述的 type_a 結構也可以設計為:

typedef struct st_type
{
    int i;
    int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));//動態開辟結構體
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));//動態開辟結構體成員
//業務處理
for(i=0; i<100; i++)
{
	p->p_a[i] = i;
}
//釋放空間
free(p->p_a);//釋放結構體成員
p->p_a = NULL;
free(p);//釋放結構體
p = NULL;

上述 代碼1 和 代碼2 可以完成同樣的功能,但是 方法1 的實現有兩個好處:
第一個好處是:方便內存釋放
如果我們的代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把整個結構體返回給
用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,所以你
不能指望用戶來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好
了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉。
第二個好處是:這樣有利于訪問速度.
連續的內存有益于提高訪問速度,也有益于減少內存碎片。

原文鏈接:https://blog.csdn.net/qq_57238876/article/details/125857770

欄目分類
最近更新