日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组

發布時間:2024/4/11 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

動態內存管理目錄:

  • 動態內存函數的介紹
  • 常見的動態內存函數的錯誤
  • 柔性數組

為什么會有動態內存管理呢

我們在日常使用中,創建一個數組,一個變量時都會開辟空間
如:

int a; //在棧上開辟一個四字節的空間char str[5]; //在棧上開辟一個五字節的連續的空間

但是上面這種開辟空間的方法都具有一個特點

1. 空間開辟的大小是固定的,無法修改
2. 聲明數組的時候必須指定長度

但是當我們在實際使用中,因為適用不同的情況,我們的空間應該是可以隨時修改來應對所有需求的,但是上面的那種方法是不能實現的,所以我們就需要動態內存管理了。

動態內存函數的介紹

malloc和free
void* malloc (size_t size);

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

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

在我們開辟一段空間后,我們也必須要將它釋放掉,而C語言同樣有這個函數

void free(void* ptr);

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

  • 如果參數ptr指向的空間不是動態開辟的,那free函數的行為是未定義的。
  • 如果參數ptr是NULL指針,則函數什么都不做

但是,free函數并不是完全銷毀這段空間,而是將這段空間的使用權收回,它可能會分配給其他東西,也可能一直放在那里不用,如果打個比方的話:我們申請一個空間,就相當于是拿到了一間房子的鑰匙,等到我們使用權結束后,我們并沒有把房子給銷毀,而是歸還房子的鑰匙,但是在歸還鑰匙之前,我們還是能夠進入這個房間,所以如果僅僅使用free,可能還不夠安全,所以應該采用下面的形式

int* arr = (int*)malloc(sizeof(int) * 5);free(arr);arr = NULL;
calloc
void* calloc (size_t num, size_t size);
  • 函數的功能是為num個大小為size的元素開辟一段空間,并把空間中的所有字節初始化為0
  • 與malloc基本一樣,只是calloc會將空間的所有字節初始化為0

在上面有提到過,我們之所以選擇動態內存管理,就是因為它可以讓我們隨心所欲的更改我們需要的空間的大小,那這個時候,我們就要用到realloc這個函數

realloc
void* realloc (void* ptr, size_t size);
  • ptr是要調整的內存地址
  • size是調整后的大小
  • 返回值為調整之后的內存起始地址
  • 這個函數在調整原內存空間大小的基礎上,還會將原來內存中的數據移動到新的空間

但是這個函數在使用的時候會有兩種情況

  • 原有空間之后有足夠大的空間
    當情況1時,我們拓展空間時會直接在原有空間后面追加一段空間

  • 原有空間之后沒有足夠大的空間

  • 當情況2時,因為后面的空間無法存放,我們就需要在堆上面找到一個足夠的,連續的空間,將我們之前的數據拷貝過去,并且返回新的空間的地址


    常見的動態內存錯誤

  • 對NULL指針的解引用操作
  • void test() {int* p = (int*)malloc(INT_MAX / 4); //申請一段巨大的空間,申請失敗所以返回NULL*p = 20; //對NULL指針進行解引用操作free(p); }

    因為當我們內存申請失敗的時候,malloc函數會返回一個NULL指針,如果我們對NULL指針進行解引用操作,就會產生錯誤,所以我們應該對malloc的返回值進行檢查,確保內存申請成功。

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

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

    對于這段函數,我們在編譯運行時可能不會報錯,但是我們在進行調試的時候就可以看到錯誤。

    因為free函數不能對非動態開辟的內存進行操作

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

    void test() {int* p = (int*)malloc(100);p++;free(p); }

    這段代碼也是,在編譯的時候沒有任何報錯,但是在運行的時候就產生了錯誤。因為我們讓指針往前移動,指向的不再是動態內存的起始位置,但是free函數必須要對動態內存的起始地址進行操作,而我們的指針p已經不再指向動態內存的起始地址了,所以產生了錯誤

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

    void test() {int* p = (int*)malloc(100);free(p);free(p); }


    我們重復free的時候,在編譯階段只會給出一個警告,但是在運行的時候就會報錯。警告的提示是使用未初始化的內存p,因為我們第一次free的時候已經將p給釋放了,所以重復free就會產生這樣的結果

    拓展

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

    這段代碼,我們乍一看是沒有錯誤的,編譯也沒有報錯沒有警告,但是在我們運行的時候就完全沒有作用。

    要了解這一點,我們就首先得知道棧幀的概念。
    當我們在調用一個函數的時候,我們會在棧上開辟一個棧幀,然后再函數結束的時候,釋放這個棧幀,而這個GetMemory調用時我們傳入的p就是在這個棧幀中作用,如果我們想讓它真的操作這個p,就應該傳這個p的地址,即一個二級指針

    修改后:

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


    這樣修改后就可以成功了

  • void GetMemory(char** p) {*p = (char*)malloc(100); } void test() {char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "hello");printf(str);} }


    這里是可以運行的,這里就是我剛剛講解的將str置為空指針的問題,因為我們free了之后這個指針就變成了一個野指針,它可能可以訪問原來的空間,也有可能不行。

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


    這里就是考察我們對于指針和數組的理解,在之前的指針那一章節中我就提到過

    char* str1 = "hello world"; //指向常量區的這段字符串 char strl2[11] = "hello world"; //在棧上開辟一段內存空間,拷貝常量區的這段字符串

    當我們在一個棧幀中,如果是str1的形式,我們返回的其實是在常量區中這段字符串的地址,而str2的方式就是其在這個棧幀中開辟的這段內存的地址,但隨著棧幀的銷毀,所以我們返回的其實是一段亂碼

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

    這樣才能得到我們想要的答案


    柔性數組

    C語言中還存在這樣一個鮮為人知的東西,在一個結構中,最后一個允許是位置大小的數組,這就叫做柔性數組成員,它的使用如下

    typedef struct st_type {int i;int a[0]; //柔性數組成員 有些編譯器不能在下標中寫入0 }type_a;
    • 結構中的柔性數組成員前面必須有至少一個其他成員
    • sizeof返回的這種結構大小不包括柔性數組的內存
    • 包含柔性數組成員的結構用malloc函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小
    柔性數組的使用

    與結構同時分配內存

    int main() {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); }

    先給結構體分配空間,再給柔性數組分配空間

    typedef struct st_type {int i;int *a; //柔性數組成員 有些編譯器不能在下標中寫入0 }type_a; int main() {int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a));p->i = 100;p->a = (int*)malloc(p->i * sizeof(int));for (i = 0; i < 100; i++){p->a[i] = i;}free(p->a);p->a = NULL;free(p);p = NULL; }

    兩段代碼作用相同,但是第一個只需要釋放一次空間,而第二個要釋放兩次。但是第二種方法也是存在好處的,就是它的的訪問速度會比第一種要快。

    總結

    以上是生活随笔為你收集整理的C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。