C語(yǔ)言動(dòng)態(tài)內(nèi)存管理-創(chuàng)新互聯(lián)

C語(yǔ)言 動(dòng)態(tài)內(nèi)存管理
  • 引言
    • C/C++程序的內(nèi)存開(kāi)辟
    • 聲明
  • 一、動(dòng)態(tài)內(nèi)存管理函數(shù)
    • 1. malloc 和 free
      • 使用示例
    • 2. calloc
      • 使用示例
    • 3. realloc
      • 使用示例
    • 4. 注意事項(xiàng)
  • 二、動(dòng)態(tài)內(nèi)存管理函數(shù)使用時(shí)的錯(cuò)誤示范
    • 程序清單1
    • 程序清單2
    • 程序清單3
    • 程序清單4
    • 程序清單5
    • 程序清單6
  • 三、經(jīng)典的內(nèi)存筆試題
    • 程序清單1
    • 程序清單2
    • 程序清單3
    • 程序清單4
    • 程序清單5

成都一家集口碑和實(shí)力的網(wǎng)站建設(shè)服務(wù)商,擁有專(zhuān)業(yè)的企業(yè)建站團(tuán)隊(duì)和靠譜的建站技術(shù),10余年企業(yè)及個(gè)人網(wǎng)站建設(shè)經(jīng)驗(yàn) ,為成都超過(guò)千家客戶(hù)提供網(wǎng)頁(yè)設(shè)計(jì)制作,網(wǎng)站開(kāi)發(fā),企業(yè)網(wǎng)站制作建設(shè)等服務(wù),包括成都營(yíng)銷(xiāo)型網(wǎng)站建設(shè),品牌網(wǎng)站制作,同時(shí)也為不同行業(yè)的客戶(hù)提供網(wǎng)站制作、成都網(wǎng)站制作的服務(wù),包括成都電商型網(wǎng)站制作建設(shè),裝修行業(yè)網(wǎng)站制作建設(shè),傳統(tǒng)機(jī)械行業(yè)網(wǎng)站建設(shè),傳統(tǒng)農(nóng)業(yè)行業(yè)網(wǎng)站制作建設(shè)。在成都做網(wǎng)站,選網(wǎng)站制作建設(shè)服務(wù)商就選創(chuàng)新互聯(lián)。引言 C/C++程序的內(nèi)存開(kāi)辟

1-1

1. 棧區(qū):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限,所以有時(shí)候會(huì)出現(xiàn)棧溢出的情況。 棧區(qū)主要存放運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回?cái)?shù)據(jù)、返回地址等。

2. 堆區(qū):一般由程序員分配、釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由操作系統(tǒng)回收,分配方式類(lèi)似于鏈表。

3. 數(shù)據(jù)段 ( 靜態(tài)區(qū) static ) 存放全局變量、靜態(tài)數(shù)據(jù),程序結(jié)束后由系統(tǒng)釋放。

4. 代碼段:存放函數(shù)體(類(lèi)成員函數(shù)和全局函數(shù))的二進(jìn)制代碼。

聲明

在我們平時(shí)寫(xiě)程序時(shí),創(chuàng)建的變量都會(huì)在底層開(kāi)辟內(nèi)存。C語(yǔ)言 的內(nèi)存布局分為三大塊:棧區(qū)、堆區(qū)、靜態(tài)區(qū)。其中棧區(qū)是我們平時(shí)使用最多的區(qū)域,棧區(qū)的使用習(xí)慣:先使用高地址處的空間,再使用低地址處的空間。然而棧區(qū)的使用總是有限的,所以本篇博客重點(diǎn)介紹如何使用堆區(qū)的內(nèi)存。

1-2

一、動(dòng)態(tài)內(nèi)存管理函數(shù) 1. malloc 和 free

malloc - memory allocate - 分配一段連續(xù)的內(nèi)存空間

void* malloc (size_t size);

// 返回值:申請(qǐng)內(nèi)存成功,則返回開(kāi)辟好空間的頭指針;申請(qǐng)內(nèi)存失敗,則返回 NULL
// 參數(shù):以字節(jié)為單位的申請(qǐng)值

free - 釋放并回收內(nèi)存

void free (void* ptr);

// 參數(shù):ptr 為先前用 malloc、calloc 或 realloc分配的內(nèi)存塊的頭指針
// 注意:一定要是頭指針,而不是中間的指針,否則就會(huì)出現(xiàn)釋放內(nèi)存不干凈的情況
使用示例
#include#includeint main() {	int* p = (int*) malloc(40); // 申請(qǐng)空間

	if (p == NULL) {// 內(nèi)存分配失敗的情況
		perror("malloc");
		return 1;
	}

	for (int i = 0; i< 10; i++) {*(p + i) = i; 		// 為剛剛開(kāi)辟的空間放入值
	}

	free(p);				// free 將剛剛系統(tǒng)開(kāi)辟的空間,再回收掉
	p = NULL;				// 為了防止 ptr 成為野指針,所以將其置成 NULL
	return 0;
}
2. calloc

calloc - 分配一段連續(xù)的內(nèi)存空間并清零

calloc 函數(shù) 和 malloc 函數(shù)的使用思想基本相同,只不過(guò)前者在分配內(nèi)存之后,又做了清零的步驟。

void* calloc (size_t num, size_t size);

// 返回值:申請(qǐng)內(nèi)存成功,則返回開(kāi)辟好空間的頭指針;申請(qǐng)內(nèi)存失敗,則返回 NULL
// 參數(shù):num 表示元素個(gè)數(shù),size 表示每個(gè)元素的字節(jié)大小
使用示例
#include#includeint main() {int* p = (int*) calloc(10, sizeof(int)); // 申請(qǐng) 10*4 = 40 個(gè)字節(jié)
	
	if (p == NULL) {perror("calloc");
		return 1;
	}

	for (int i = 0; i< 10; i++) {printf("%d ", *(p + i));
	}
	
	free(p); 	// 釋放
	p = NULL;

	return 0;
}

// 輸出結(jié)果:0 0 0 0 0 0 0 0 0 0
3. realloc
void* realloc (void* ptr, size_t size);

使用 realloc 再次申請(qǐng)分配內(nèi)存的時(shí)候,需要指定重新開(kāi)辟內(nèi)存的字節(jié)總大小。假設(shè)我們之前使用 malloc / calloc 申請(qǐng)內(nèi)存時(shí),內(nèi)存不夠用了,realloc 就會(huì)派上用場(chǎng)。它可以做到很人性化地開(kāi)辟內(nèi)存。

情況①:當(dāng) malloc / calloc 申請(qǐng)的內(nèi)存后面,有足夠的空間再次擴(kuò)充,realloc 就能夠直接接著上次的位置,繼續(xù)開(kāi)辟。最終還是返回上一次開(kāi)辟的原地址。

情況②:當(dāng) malloc / calloc 申請(qǐng)的內(nèi)存后面,沒(méi)有足夠的空間再次擴(kuò)充,realloc 也能夠另起一塊內(nèi)存區(qū)域,將之前申請(qǐng)的內(nèi)存中的數(shù)據(jù)也一并放入到新的內(nèi)存區(qū)域中。此時(shí)最終返回的是新的地址。

使用示例
#include#includeint main() {int* p = (int*) malloc(40);      // 1

	if (p == NULL) {perror("malloc");
		return 1;
	}

	for (int i = 0; i< 10; i++) {*(p + i) = i;
	}

	int* ptr = (int*)realloc(p, 80); // 2
	if (ptr != NULL) {		 // 3
		p = ptr;
	}
	
	free(p);						 // 4			
	p = NULL;			
	return 0;
}

// 1. malloc 申請(qǐng)分配內(nèi)存。

// 2. 假設(shè)空間不夠,使用 realloc 再次申請(qǐng)。

// 3. 由于內(nèi)存有限,所以 realloc 也有可能申請(qǐng)內(nèi)存失敗,當(dāng)失敗的時(shí)候就會(huì)返回 NULL. 只有當(dāng) ptr 不為空的時(shí)候,才能夠放入 p 中,這樣就能保證 p 不受影響。

// 4. 釋放內(nèi)存

4. 注意事項(xiàng)

申請(qǐng)內(nèi)存分配后,始終需要記得使用 free. 當(dāng)我們不釋放動(dòng)態(tài)申請(qǐng)的內(nèi)存的時(shí)候,如果程序結(jié)束,動(dòng)態(tài)申請(qǐng)的內(nèi)存由操作系統(tǒng)自動(dòng)回收;但如果程序不結(jié)束,動(dòng)態(tài)內(nèi)存是不會(huì)自動(dòng)回收的,就會(huì)形成內(nèi)存泄露的問(wèn)題,久而久之,內(nèi)存就會(huì)被 " 耗干 "。

雖然現(xiàn)在的操作系統(tǒng)很智能,能夠限制一些程序的內(nèi)存使用,但還是建議標(biāo)準(zhǔn)化。

二、動(dòng)態(tài)內(nèi)存管理函數(shù)使用時(shí)的錯(cuò)誤示范 程序清單1

動(dòng)態(tài)內(nèi)存申請(qǐng)失敗,對(duì) NULL 直接進(jìn)行了解引用操作。

#include#include#includeint main () {int* p = malloc(INT_MAX); // 申請(qǐng)失敗
	*p = 100; // error

	free(p);
	p = NULL;

	return 0;
}
程序清單2

對(duì)動(dòng)態(tài)開(kāi)辟空間的越界訪問(wèn)。

#include#includeint main() {int* p = malloc(40); // 開(kāi)辟 40 個(gè)字節(jié)的內(nèi)存
	if (p == NULL) {perror("malloc");
		return 1;
	}

	// 越界訪問(wèn) 41 ~ 44
	for (int i = 0; i< 11; i++) {*(p + i) = i;
	}

	free(p);
	p = NULL;

	return 0;
}
程序清單3

對(duì)非動(dòng)態(tài)開(kāi)辟內(nèi)存使用 free 釋放.

#include#includeint main() {int a = 10;
	int* p = &a;

	free(p); // error
	p = NULL;

	return 0;
}
程序清單4

free 釋放不完整,或者 free 函數(shù)接收的指針不處于內(nèi)存開(kāi)辟的頭部。

#include#includeint main() {int* p = (int*) malloc(100);
	if (p == NULL) {perror("malloc");
		return 1;
	}

	for (int i = 0; i< 10; i++) {*p = i;
		p++;
	}

	free(p); // error
	p = NULL;

	return 0;
}

從第 40 個(gè)字節(jié)后面開(kāi)始釋放內(nèi)存,那么前面的內(nèi)存就未被釋放完。

1-3

程序清單5

對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放。

#include#includeint main() {int* p = malloc(100);

	if (p == NULL) {perror("malloc");
		return 1;
	}
	
	free(p);
	free(p); // error
	p = NULL;

	return 0;
}
程序清單6

內(nèi)存泄漏。

如下程序,在 test 函數(shù)中,即使我們寫(xiě)了 free,但最終走不到釋放的代碼,依然會(huì)存在內(nèi)存泄漏的問(wèn)題。所以說(shuō),free 代碼的放置位置很關(guān)鍵,并不是寫(xiě)了 free 就一定能夠釋放動(dòng)態(tài)內(nèi)存。

#include#includevoid test() {int* p = (int*)malloc(100);
	if (1) {return;
	}

	free(p); // 代碼走不到這一行
	p = NULL;
}

int main() {test();
	return 0;
}
三、經(jīng)典的內(nèi)存筆試題 程序清單1
#include#include#includevoid getMemory(char* p)
{p = (char*) malloc(100);
}

int main() {char* str = NULL;
	getMemory(str);
	strcpy(str, "hello world"); // 對(duì)空指針解引用

	printf(str);

	return 0;
}

分析:

在 getMemory 函數(shù)中,在堆區(qū)申請(qǐng)了 100 個(gè)字節(jié)的內(nèi)存大小,之后我們可以通過(guò)指針 p 來(lái)找到這些內(nèi)存。然而,在主函數(shù)中,str 始終為 NULL. 也就是說(shuō),str 并沒(méi)有指向任何一塊區(qū)域,所以后面在使用 strcpy 函數(shù)的時(shí)候," hello world " 就不能成功拷貝。

這題錯(cuò)誤的主要原因就是 str 為實(shí)參,p 為形參,對(duì)形參的操作不影響實(shí)參。

1-4

程序清單2
#include#include#includevoid getMemory(char** p, int num)
{*p = (char*) malloc(num); // 相當(dāng)于 str 指向了開(kāi)辟的 100 個(gè)字節(jié)的空間
	// *(&str)<==>str = (char*) malloc(100); 
}

int main() {char* str = NULL;
	getMemory(&str, 100);
	
	strcpy(str, "hello");
	printf(str);

	free(str); 			
	str = NULL; 		

	return 0;
}

// 輸出結(jié)果: hello

分析:

程序清單2 算是 對(duì) 程序清單1 的優(yōu)化,在 getMemory 函數(shù)中,當(dāng)我們傳過(guò)去的是 str 的地址時(shí),再次對(duì)二級(jí)指針解引用,就相當(dāng)于能夠通過(guò) str 直接找到開(kāi)辟的內(nèi)存。上面的程序沒(méi)有問(wèn)題,但是我們也不要忘記 free 來(lái)幫助系統(tǒng)回收內(nèi)存了。

1-5

程序清單3
#include#include#includechar* getMemory()
{char p[] = "hello world";
	return p;
}

int main() {char* str = NULL;
	str = getMemory();
	printf(str);

	return 0;
}

// 輸出結(jié)果:隨機(jī)值

分析:

數(shù)組 p 在棧區(qū)開(kāi)辟了內(nèi)存,但出了 getMemory 函數(shù)后,數(shù)組中之前所占用的空間就被操作系統(tǒng)銷(xiāo)毀了,也就是說(shuō),剛剛棧區(qū)的內(nèi)存還給了系統(tǒng),現(xiàn)在出了函數(shù)后,即使知道這塊內(nèi)存的地址,但已經(jīng)沒(méi)有權(quán)限再次使用這塊區(qū)域了。因?yàn)閿?shù)組 p 是一個(gè)臨時(shí)的局部變量。

p 表示數(shù)組的首元素地址,假設(shè) p 的地址為 0x11223344,那么出了 getMemory 函數(shù)后,現(xiàn)在 0x11223344 指向的內(nèi)存可能已經(jīng)被其他數(shù)據(jù)占用了。所以在主函數(shù)中,當(dāng) str 再次指向 0x11223344,找到的就是一些隨機(jī)存放的數(shù)據(jù)。

1-6

程序清單4
#includeint* test() {int a = 10;
	return &a;
	// 出了 test 函數(shù),a 的地址將被系統(tǒng)回收
}

int main() {int* p = test();

	return 0;
}

程序清單4 和 程序清單3 的錯(cuò)誤思想基本相同,這是典型的 【返回??臻g地址、野指針】的問(wèn)題。a 的地址出了 test 函數(shù)后,就需要被回收,所以后續(xù)在主函數(shù)中,指針 p 就沒(méi)有權(quán)限去操作這塊內(nèi)存區(qū)域。

為什么棧區(qū)的地址最后需要被回收?
答:因?yàn)楹瘮?shù)和局部變量在使用時(shí),需要在棧區(qū)開(kāi)辟內(nèi)存,當(dāng)用完了,如果不被回收,那么操作系統(tǒng)的內(nèi)存遲早要被耗盡。

舉個(gè)例子:這就和住酒店一樣,當(dāng) 旅客 A 租完了房子,房子就需要打掃干凈,騰出來(lái)給后面的 旅客B / 旅客C / 旅客D… 使用。否則,房子永遠(yuǎn)不夠后面的旅客使用。如果 旅客A 前腳剛走,旅客B 就住進(jìn)去了,那么這個(gè)房子暫時(shí)屬于 旅客B 的,假設(shè) 旅客A 發(fā)現(xiàn)自己手機(jī)留在了剛剛的房子忘拿了,那么 旅客A 就需要和前臺(tái)報(bào)備一下,如果 旅客A 直接沖進(jìn)去了剛剛的房子找手機(jī),旅客B 就會(huì)告他非法訪問(wèn)。

程序清單5
#includevoid test(){char* str = (char*) malloc(100);
	strcpy(str, "hello");
	free(str);
	
	if (str != NULL){		
		// 野指針,非法操作
		strcpy(str, "world");
		printf(str);
	}
}

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

上面的代碼依舊是野指針問(wèn)題,當(dāng) malloc 開(kāi)辟的堆區(qū)內(nèi)存區(qū)域被 free 回收之后,str 指向的地址依舊是剛剛申請(qǐng)的頭部地址,但此時(shí)卻沒(méi)有了操作這塊內(nèi)存的權(quán)限。所以將 " world " 字符串放入 str 指向的區(qū)域,最終就會(huì)造成非法訪問(wèn)。

綜上所述,我們應(yīng)該在 free 的后面添加如下代碼:

str = NULL; // 防止 str 成為一個(gè)野指針

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧

本文標(biāo)題:C語(yǔ)言動(dòng)態(tài)內(nèi)存管理-創(chuàng)新互聯(lián)
文章鏈接:http://bm7419.com/article0/dpogio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站營(yíng)銷(xiāo)微信公眾號(hào)、面包屑導(dǎo)航、ChatGPT搜索引擎優(yōu)化、軟件開(kāi)發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司