C語言編程動態內存分配常見錯誤全面分析

前言：為什麼存在動態內存分配？

int a=10;//在棧空間上開辟4字節
char arr={0};//在棧空間上開辟10字節連續空間

1.空間開辟的大小是固定的。
2.數組在聲明的時候，必須指定數組長度，它所需要的內存在編譯時進行分配。

一、動態內存函數 （圖片來自比特就業課）

1.malloc和free函數

malloc函數聲明：

void*malloc(size_t size);//size_t即unsigned int

1.如果開辟成功，返回一個指向開辟空間的指針
2.如果開辟失敗，返回一個NULL指針
（由於是NULL指針，所以對於malloc函數常用assert進行檢查）
3.返回值類型為void*,所以malloc函數開辟空間的類型需要使用者自己進行決定
4.如果size=0，malloc的行為取決具體編譯器

free函數聲明

void free(void*ptr);

free函數用來釋放動態開辟的空間，註意點如下：
1.如果參數ptr指向的空間不是動態開辟的，那free函數的行為是未知的
2.如果ptr是空指針，free函數什麼也不做

#include<stdio.h>
#include<stdlib.h>//malloc和free函數頭文件
int main()
{
int*p=(int*)malloc(40);//malloc出來的空間是不確定類型的，需要你自己強轉一個類型
//這裡我們把它轉換成int*,由整型指針對這塊空間進行維護
//那這裡會是4個字節看成一個元素,40個字節，可填充10個int元素
if (p == NULL)
{
return -1;
}//如果沒有返回值則說明開辟成功
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i)=i;//對開辟空間進行賦值操作
}
free(p);//用完之後不需要瞭，用free函數釋放p所指向的空間（40個字節全部釋放掉，還給操作系統）
p = NULL;//由於p存儲的是開辟空間的地址，即使空間還給瞭操作形態，但p還是可以找到這塊空間，這是非常危險的，所以我們用一個空指針賦給p
return 0;
}

2.calloc函數

calloc函數也是用來動態內存分配的，函數聲明如下

void*calloc(size_t num,size_t size)

#include<stdio.h>
#include<stdlib.h>//calloc函數頭文件
#include<string.h>//strerror函數頭文件
#include<errno.h>//errno頭文件
int main()
{
int*p = (int*)calloc(10, sizeof(int));//開辟10個大小為int型的空間
if (p == NULL)//calloc函數也有可能開辟空間失敗，需要進行檢驗
{
printf("%s\n", strerror(errno));//errno是錯誤碼，strerror會把錯誤碼轉換成相應的錯誤信息
return -1;
}
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *(p + i));//會打印10個0（calloc會自己初始化為0）
}
free(p);
p = NULL;
return 0;
}

3.realloc函數

realloc函數是在已有空間不夠的情況下，進行追加申請空間的函數，其聲明如下：

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

realloc函數是為瞭讓動態內存管理更加靈活，有時候我們發現之前申請的空間過少瞭，或者過大瞭，我們可以用realloc函數來進行增減，它的註意點如下：
1.ptr是要調整的內存地址
2.size是調整之後新的大小
3.返回值是調整後的內存起始位置
4.該函數在調整原先內存大小的基礎上，還會將原來內存的數據復制到新空間
5.realloc函數在調整內存空間存在兩種情況 #include<stdio.h>
#include<stdlib.h>//malloc和free函數頭文件
int main()
{
int*p=(int*)malloc(40);//開辟10個int大小的空間
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i)=i;//對開辟空間進行賦值操作
}
int*ptr = realloc(p, 20 * sizeof(int));//註意！這裡20*sizeof（int）是新的大小
//比如我現在隻有10個，我需要20個，差10個。但這裡不是寫10，而是寫新的大小20
if (ptr != NULL)
{
p = ptr;
}
for (i = 10;i < 20;i++)//對新開辟空間賦值
{
*(p + i) = i;
}
for (i = 0;i < 20;i++)
{
printf("%d ", *(p + i));//打印0-19
}
free(p);
p = NULL;
return 0;
}

二、常見錯誤

1.對NULL指針解引用

#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(20);
*p = 0;//對p這個地址解引用並賦值為0
free(p);
return 0;
}

malloc等等函數在開辟空間時都是有可能開辟失敗的，萬一失敗，就是返回空指針，你直接對空指針解引用並賦值肯定是有問題的

#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(20);
if(p==NULL)
{
return -1;
//如果返回，開辟失敗結束程序，如果沒有返回則可進行下面的操作
}
*p = 0;//對p這個地址解引用並賦值為0
free(p);
return 0;
}

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(200);//200個字節也就是50個int型
if (p == NULL)
{
return -1;
}
int i = 0;
for (i = 0;i < 60;i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}

3.對非動態開辟使用free函數

#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = 10;
int*p = &a;
free(p);
p = NULL;
}

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

//使用free釋放一塊動態內存開辟內存的一部分
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
int i = 0;
for (i = 0;i < 5;i++)
{
*p++ = i;
}
//釋放
free(p);
p = NULL;
} 5.對同一塊空間多次釋放

#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
free(p);
free(p);
}
#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
free(p);
p = NULL;
free(p);
}

6.動態開辟內存忘記釋放

1.你自己free掉
2.程序退出時，系統自動回收

#include<stdio.h>
#include<stdlib.h>
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
getchar();
return 0;
}
{"ft_lang_label":"__label__zh","ft_lang_prob":0.82718825,"math_prob":0.9938624,"size":5104,"snap":"2022-05-2022-21","text_gpt3_token_len":4210,"char_repetition_ratio":0.15862745,"word_repetition_ratio":0.33718246,"special_character_ratio":0.28683385,"punctuation_ratio":0.18205129,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9794123,"pos_list":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"im_url_duplicate_count":[null,1,null,1,null,1,null,1,null,1,null,1,null,1,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2022-01-23T11:54:28Z\",\"WARC-Record-ID\":\"<urn:uuid:eb71d85c-6deb-48f6-8814-43313c8da3f7>\",\"Content-Length\":\"45979\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:0da0f440-1301-4e60-8c26-0c6efdaa1328>\",\"WARC-Concurrent-To\":\"<urn:uuid:1c5ea923-24c2-418b-b85d-ad4e934c8448>\",\"WARC-IP-Address\":\"104.21.17.50\",\"WARC-Target-URI\":\"https://walkonnet.com/archives/461110\",\"WARC-Payload-Digest\":\"sha1:AK2JBPEPNZ5WYRFUJBGE3CKC4F3XVKSD\",\"WARC-Block-Digest\":\"sha1:YQPFIFGFBLHV2QWD6VIFCJN76QZ75SF2\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2022/CC-MAIN-2022-05/CC-MAIN-2022-05_segments_1642320304261.85_warc_CC-MAIN-20220123111431-20220123141431-00424.warc.gz\"}"}