亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文解析C語言中動態(tài)內存管理

 更新時間:2024年02月18日 09:51:34   作者:Betty’sSweet  
這篇文章主要為大家詳細介紹了C語言中動態(tài)內存管理的相關知識,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以跟隨小編一起學習一下

1. 靜態(tài)開辟內存

通過前面的學習,我們已經掌握了兩種開辟內存的方法,分別是:

#include<stdio.h>
int main()
{
	int val = 20; //在??臻g上開辟四個字節(jié)
	char arr[10] = { 0 }; //在??臻g上開辟10個字節(jié)的連續(xù)空間
	return 0;
}

但是靜態(tài)開辟的空間明顯有兩個缺陷:

  • 空間開辟??是固定的。
  • 數組在申明的時候,必須指定數組的?度,數組空間?旦確定了??不能調整。

2. 動態(tài)內存

為了解決靜態(tài)內存開辟的內存空間固定的問題,C語言引?了動態(tài)內存開辟,讓程序員??可以申請和釋放空間,就?較靈活了。

2.1 動態(tài)內存開辟函數

(1) malloc函數

頭文件#include <stdlib.h>

聲明:void* malloc (size_t size);

  • size -- 內存塊的大小,以字節(jié)為單位
  • 如果參數 size 為0,malloc的?為是標準是未定義的,取決于編譯器。

作用:向內存申請?塊連續(xù)可?的空間,并返回指向這塊空間的指針

  • 如果開辟成功,則返回?個指向開辟好空間的指針。
  • 如果開辟失敗,則返回?個 NULL 指針,因此malloc的返回值?定要做檢查。

返回值:返回值的類型是 void* ,所以malloc函數并不知道開辟空間的類型,具體在使?的時候使?者??來決定。

補充打印錯誤信息函數:perror()

頭文件:#include <stdio.h>

聲明:void perror(const char *str)

str -- 這是 C 字符串,包含了一個自定義消息,將顯示在原本的錯誤消息之前。

作用:把一個描述性錯誤消息輸出到標準錯誤 stderr。首先輸出字符串 str,后跟一個冒號,然后是一個空格。

返回值:無返回值。

下列是malloc與perror的具體使用方法:

int main()
{
	int* arr = (int*)malloc(sizeof(int) * 10);
	//開辟十個大小為整型的空間
	//返回類型強轉為int*
	if (arr == NULL)//如果開辟失敗
	{
		perror("malloc fail: ");//打印錯誤信息
                 return 1;//直接返回
	}
	int i = 0;
	for (i = 0; i < 10; i++)//存入數據
	{
		arr[i] = i;
	}
	for (i = 0; i < 10; i++)//打印數據
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

輸出結果:

監(jiān)視觀察:

動態(tài)內存的數據存放在堆區(qū)

(2) calloc函數

頭文件:#include <stdlib.h>

聲明:void *calloc(size_t nitems, size_t size)

  • nitems -- 要被分配的元素個數。
  • size -- 元素的大小。

作用: 分配所需的內存空間,并返回一個指向它的指針

返回值:該函數返回一個指針,指向已分配的內存。如果請求失敗,則返回 NULL。

malloc 和 calloc 之間的不同點是,malloc 不會設置內存為零,而 calloc 會設置分配的內存為零。

下列是calloc的使用實例:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//開辟十個大小為整型的空間
	//返回類型強轉為int*
	if (arr == NULL)//如果開辟失敗
	{
		perror("calloc fail: ");//打印錯誤信息
                 return 1;//直接返回
	}
	return 0;
}

calloc的初始化觀察:

(3) realloc函數

頭文件:#include <stdlib.h>

聲明:void *realloc(void *ptr, size_t size)

  • ptr -- 指針指向一個要重新分配內存的內存塊,該內存塊之前是通過調用 malloc、calloc 或 realloc 進行分配內存的。如果為空指針,則會分配一個新的內存塊,且函數返回一個指向它的指針。
  • size -- 內存塊的新的大小,以字節(jié)為單位。如果大小為 0,且 ptr 指向一個已存在的內存塊,則 ptr 所指向的內存塊會被釋放,并返回一個空指針。

作用:嘗試重新調整之前調用 malloc 或 calloc 所分配的 ptr 所指向的內存塊的大小。

返回值:該函數返回一個指針 ,指向重新分配大小的內存。如果請求失敗,則返回 NULL。

  • 有時會我們發(fā)現過去申請的空間太?了,有時候我們?會覺得申請的空間過?了,那為了合理的時候內存,我們?定會對內存的??做靈活的調整。那 realloc 函數就可以做到對動態(tài)開辟內存??的調整。
  • realloc擴容機制:
  • 本地擴容:原有空間之后有?夠?的空間,直接在原有內存之后直接追加空間,原來空間的數據不發(fā)?變化。

異地擴容:原有空間之后沒有?夠?的空間,在堆空間上另找?個合適??的連續(xù)空間。將新增數據與原本數據拷貝過來,并自動釋放原來空間。

下列是realloc的具體使用方法:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//開辟十個大小為整型的空間
	//返回類型強轉為int*
	if (arr == NULL)//如果開辟失敗
	{
		perror("calloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	//繼續(xù)新增空間
	int* tmp = (int*)realloc(arr, sizeof(int) * 15);
        //不用arr是為了防止開辟失敗,被至為NULL
	if (tmp == NULL)//如果開辟失敗
	{
		perror("calloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	arr = tmp;
	return 0;
	
}

新增內存較小時一般是在原有基礎上新增空間。兩者地址相同。

int* tmp = (int*)realloc(arr, sizeof(int) * 100);//新增內存較大時

新增內存較大時則會重新開辟一段空間,將原來的空間釋放。兩者地址不同。

2.2 動態(tài)內存釋放函數

動態(tài)內存開辟的空間并不像靜態(tài)開辟內存的空間會隨著一段程序的結束而回收,這時就需要我們手動回收,否則就會造成內存泄漏。

內存泄漏(Memory Leak)是指程序中已動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。

頭文件:#include <stdlib.h>

聲明:void free(void *ptr)

ptr -- 指針指向一個要釋放內存的內存塊,該內存塊之前是通過調用 malloc、calloc 或 realloc 進行分配內存的。如果傳遞的參數是一個空指針,則不會執(zhí)行任何動作。

作用:釋放之前調用 calloc、malloc 或 realloc 所分配的內存空間。

返回值:該函數不返回任何值。

下面使用free函數的實例:

int main()
{
	int* arr = (int*)calloc(10, sizeof(int));
	//開辟十個大小為整型的空間
	//返回類型強轉為int*
	if (arr == NULL)//如果開辟失敗
	{
		perror("calloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	//繼續(xù)新增空間
	int* tmp = (int*)realloc(arr, sizeof(int) * 100);

	if (tmp == NULL)//如果開辟失敗
	{
		perror("calloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	arr = tmp;
	free(arr);//釋放arr所指向的內存
	arr = NULL;
	return 0;
	
}

釋放完之后記得將arr置為NULL,否則arr指向一段已經回收的空間會變成野指針。

2.3 常見內存分布

?般我們在學習C/C++語?的時候,我們會關注內存中的三個區(qū)域:棧區(qū)、 堆區(qū)、靜態(tài)區(qū)。

  • 局部變量與函數參數是放在內存的棧區(qū),
  • 全局變量,static修飾的變量是放在內存的靜態(tài)區(qū)。
  • 堆區(qū)是?來動態(tài)內存管理的。

具體分布如下圖:

3. 動態(tài)內存的常見錯誤

動態(tài)內存開辟就像指針一樣,一不小心就會釀成大錯,以下介紹了一些常見的內存開辟錯誤:

3.1 對NULL指針的解引用

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20; //如果p的值是NULL,就會有問題
	free(p);
}
  • INT_MAX是一個宏定義,他表示整型的最大值,值為2147483647
  • 當malloc申請的空間太大時存在失敗的情況,失敗返回NULL指針。
  • 而系統(tǒng)無法訪問NULL指針指向的地址,這時編譯器會報一個警告:

改正方法:

void test()
{
    int* p = (int*)malloc(INT_MAX / 4);
    if (NULL == p)
    {
        perror("malloc fail: ");//打印錯誤信息
        return 1;
    }
    *p = 20;
    free(p);
    p = NULL;
}

這時就體現判斷是否為空指針的重要性了

3.2 對動態(tài)開辟空間的越界訪問

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i; //當i是10的時候越界訪問
	}
	free(p);
         p=NULL;
}
  • malloc只申請了十個整型大小的空間。
  • for循環(huán)循環(huán)了十一次,越界訪問,錯誤信息如下:

改正方法:

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail: ");//打印錯誤信息
		return 1;//直接返回
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i; //當i是10的時候越界訪問
	}
	free(p);
	p = NULL;
}

3.3 對非動態(tài)開辟內存使用free釋放

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
         p=NULL;//ok?
}
  • free()只能釋放有動態(tài)內存開辟在堆上的空間。
  • p指向的空間是靜態(tài)內存開辟的,無法釋放,釋放就會出錯:

改正方法:

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

靜態(tài)內存開辟的空間并不需要釋放。

3.4 使?free釋放?塊動態(tài)開辟內存的?部分

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p); //p不再指向動態(tài)內存的起始位置
	p = NULL;
}
  • p++跳過一個整型大小的空間。
  • free()釋放p只會釋放當前位置開始之后的空間,有一個整型大小的空間未被釋放,造成內存泄漏。

改正方法:

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

不能隨意改變p指向的位置,開辟多少內存就釋放多少內存

3.5 對同?塊動態(tài)內存多次釋放

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p); //重復釋放
}
  • p已經被釋放歸還給操作系統(tǒng),但是此時p還指向該內存,是一個野指針。
  • 再次釋放p就會出現內存出錯問題。

改正方法:

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

釋放內存之后記得將其置為空指針,這樣再次free空指針就不會進行任何操作。

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

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}//內存泄漏
}

int main()
{
	test();
}

當我們動態(tài)內存申請空間之后必須手動將其釋放,不會就會出現內存泄漏的問題。

改正方法:

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

每次使用完動態(tài)內存開辟空間之后記得釋放內存。

4. 相關筆試題

4.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仍是空指針,而strcpy拷貝會對空指針進行解引用操作,對NULL指針解引用會出錯!

內存泄漏:在GetMemory()函數內部動態(tài)申請了100字節(jié)的空間,因為p隨著函數結束而被銷毀,所以已經再也找不到該空間,會造成內存泄漏。

改正方法:

  • 我們要想改變str就需要傳址調用,而str本身就是個指針變量,傳指針變量的地址需要二級指針來接收。
  • 使用完之后必須釋放內存。
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	// 釋放
	free(str);
	str = NULL;
}

4.2 題目二

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

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

這段程序是經典的野指針問題,局部變量出了作用就會銷毀歸還給操作系統(tǒng),而str還能指向這塊空間就會形成野指針。

改正方法:

因為只有存放在棧區(qū)的值才會被銷毀,所以我們將其放在其他區(qū)域如:靜態(tài)區(qū),而放在靜態(tài)區(qū)有兩種方法:static修飾與常量字符串。

 const char* GetMemory1(void)
{
	const char* p = "hello world";
	return p;
}
 char* GetMemory2(void)
 {
	 static char p[] = "hello world";
	 return p;
 }

void Test(void)
{
	char* str = NULL;
	str = GetMemory1();
	printf(str);
	printf("\n");
	str = GetMemory2();
	printf(str);
}

int main()
{
	Test();
	return 0;
}

輸出結果:

4.3 題目三

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
//請問運?Test函數會有什么樣的結果?

這又是一個經典的內存泄漏問題——p開辟出內存未被釋放。

改正方法:

void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	free(str);
	str = NULL;
}

4.4 題目四

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
//請問運?Test函數會有什么樣的結果?

這也是個經典野指針問題,str所開辟的空間已經歸還給了操作系統(tǒng),這時再將world拷貝進str就會出錯。

改正方法:

歸還內存之后隨手將其值為NULL指針,后續(xù)語句就不會進行。

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

5. 柔性數組

5.1 柔性數組是什么

C99中,結構體中的最后一個元素允許是未知大小的數組,這就叫作柔性數組,例如:

typedef struct st_type
{
	int i;
	int a[0];	//柔性數組成員
}type_a;

有些編譯器會報錯?法編譯可以改成:

typedef struct st_type
{
	int i;
	int a[];	//柔性數組成員
}type_a;
  • 結構中的柔性數組成員前?必須?少?個其他成員。
  • 包含柔性數組成員的結構?malloc()函數進?內存的動態(tài)分配,并且分配的內存應該?于結構的??,以適應柔性數組的預期??。

5.2 柔性數組的大小

依靠我們結構體學過得內存對齊的原則,我們可以計算結構體的大小。

typedef struct st_type
{
	int i;
	int a[0]; //柔性數組成員
}type_a;
int main()
{
	printf("%d\n", sizeof(type_a)); 
	return 0;
}

輸出結果:

從上述可知柔性數組成員是不計入結構體大小的。

5.3 柔性數組的使用

柔性數組的使用與結構體使用十分類似,具體使用如下:

#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
	int i;
	int a[]; //柔性數組成員
}type_a;
int main()
{
	int i = 0;
	type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
	//包含柔性數組成員的結構?**malloc()函數**進?內存的動態(tài)分配,
	// 并且分配的內存應該?于結構的??,以適應柔性數組的預期??。
	p->i = 100;
	for (i = 0; i < 100; i++)//存放數據
	{
		p->a[i] = i;
	}
	free(p);
	return 0;
}

5.4 模擬實現柔性數組

先開辟一個結構體大小,在開辟一個數組的大小。

柔性數組成員的空間都是malloc開辟的,所以模擬的柔性數組也需要malloc開辟。

具體實施如下:

#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
	int i;
	int* p_a;
}type_a;
int main()
{
	//先開辟一個結構體大小
	type_a* p = (type_a*)malloc(sizeof(type_a));
	p->i = 100;
	//在開辟一個數組大小
	p->p_a = (int*)malloc(p->i * sizeof(int));
	for (int i = 0; i < 100; i++)
	{
		p->p_a[i] = i;
	}
	//釋放空間
	free(p->p_a);
	p->p_a = NULL;
	free(p);
	p = NULL;
	return 0;
}

5.5 柔性數組的優(yōu)勢

通過與模擬的柔性數組對比,我們可以看出柔性數組的優(yōu)勢:

便內存釋放: 如果我們的代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把整個結構體返回給用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,容易造成內存泄漏。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉

這樣有利于訪問速度: 連續(xù)的內存有益于提?訪問速度,也有益于減少內存碎?。

以上就是一文解析C語言中動態(tài)內存管理的詳細內容,更多關于C語言動態(tài)內存管理的資料請關注腳本之家其它相關文章!

相關文章

  • C語言練習之掃雷小游戲

    C語言練習之掃雷小游戲

    這篇文章主要為大家詳細介紹了C語言練習之掃雷小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • C語言結構體的全方面解讀

    C語言結構體的全方面解讀

    C 數組允許定義可存儲相同類型數據項的變量,結構是 C 編程中另一種用戶自定義的可用的數據類型,它允許你存儲不同類型的數據項
    2021-10-10
  • opengl實現任意兩點間畫圓柱體

    opengl實現任意兩點間畫圓柱體

    這篇文章主要為大家詳細介紹了opengl實現任意兩點間畫圓柱體,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • C++?std::chrono庫使用示例(實現C++?獲取日期,時間戳,計時等功能)

    C++?std::chrono庫使用示例(實現C++?獲取日期,時間戳,計時等功能)

    std::chrono是C++標準庫中的一個組件,用于表示和處理時間,這篇文章主要介紹了C++?std::chrono庫使用指南(實現C++?獲取日期,時間戳,計時等功能),需要的朋友可以參考下
    2023-06-06
  • C++文件的操作及小實驗示例代碼詳解

    C++文件的操作及小實驗示例代碼詳解

    這篇文章主要介紹了C++文件的操作及小實驗,對于文件,它是一個流對象,對文件的操作無非是讀和寫,通過本文的學習大家將會理解文件的具體操作
    2022-05-05
  • C語言實現校園導游系統(tǒng)

    C語言實現校園導游系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現校園導游系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C語言中輸出空格的五種方法

    C語言中輸出空格的五種方法

    在C語言中,輸出空格可以通過使用?printf?函數來實現,可以直接在字符串中包含空格,或者使用轉義字符來控制格式,本文給大家介紹了幾種常見的輸出空格的方法,需要的朋友可以參考下
    2024-08-08
  • C語言實現學生信息管理系統(tǒng)(多文件)

    C語言實現學生信息管理系統(tǒng)(多文件)

    這篇文章主要為大家詳細介紹了C語言實現學生信息管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • C++繼承介紹

    C++繼承介紹

    C++繼承可以是單一繼承或多重繼承,每一個繼承連接可以是public,protected,private也可以是virtual或non-virtual
    2013-01-01
  • 詳解C++ new-handler機制

    詳解C++ new-handler機制

    這篇文章主要介紹了C++ new-handler機制的相關資料,幫助大家更好的理解和使用c++,感興趣的朋友可以了解下
    2020-11-11

最新評論