1.動(dòng)態(tài)內(nèi)存函數(shù)
為什么存在動(dòng)態(tài)內(nèi)存分配?
int main(){
int num = 10; //向棧空間申請(qǐng)4個(gè)字節(jié)
int arr[10]; //向??臻g申請(qǐng)了40個(gè)字節(jié)
return 0;
}
上述的開(kāi)辟空間的方式有兩個(gè)特點(diǎn):
- 空間開(kāi)辟大小是固定的。
- 數(shù)組在聲明的時(shí)候,必須指定數(shù)組的長(zhǎng)度,它所需要的內(nèi)存在編譯時(shí)分配。
但是對(duì)于空間的需求,不僅僅是上述的情況。有時(shí)候我們需要的空間大小在程序運(yùn)行的時(shí)候才能知道,那數(shù)組的編譯時(shí)開(kāi)辟空間的方式就不能滿足了。這時(shí)候就需要?jiǎng)討B(tài)內(nèi)存開(kāi)辟了。
1.1 malloc和free
malloc()
是用于在程序執(zhí)行期間動(dòng)態(tài)分配內(nèi)存。它的全稱(chēng)是"memory allocation",意為內(nèi)存分配。malloc()
函數(shù)是C標(biāo)準(zhǔn)庫(kù)的一部分,它的聲明在stdlib.h
頭文件中。
函數(shù)原型如下:
void* malloc(size_t size);
在這里,size
是你想要分配的字節(jié)數(shù),函數(shù)返回一個(gè)指向分配的內(nèi)存塊起始地址的指針。malloc()
函數(shù)的返回類(lèi)型是void*
,這意味著返回的指針可以賦值給任何指針類(lèi)型而無(wú)需顯式轉(zhuǎn)換。
下面簡(jiǎn)要解釋一下malloc()
的工作原理:
1.你提供想要分配的字節(jié)數(shù),
malloc()
在堆內(nèi)存中搜索一個(gè)足夠大的連續(xù)內(nèi)存塊來(lái)存儲(chǔ)這些字節(jié)。2.如果找到了合適的內(nèi)存塊,它將其標(biāo)記為已使用,并返回該內(nèi)存塊的起始地址的指針。
3.如果找不到足夠大的內(nèi)存塊,它將返回一個(gè)
NULL
指針,表示內(nèi)存分配失敗。
注意:使用malloc()
分配的內(nèi)存需要使用free()
函數(shù)顯式地釋放,否則會(huì)導(dǎo)致內(nèi)存泄漏。
void free(void* ptr);
free()
函數(shù)接受之前分配的內(nèi)存塊的指針,并將其釋放,使其可供將來(lái)的動(dòng)態(tài)分配使用。如果忘記釋放之前分配的內(nèi)存,程序每次運(yùn)行分配代碼時(shí)都會(huì)消耗更多內(nèi)存,最終可能導(dǎo)致內(nèi)存耗盡。
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int* dynamicArray = (int*)malloc(n * sizeof(int));
if (dynamicArray == NULL) {
printf("內(nèi)存分配失??!\n");
} else {
// 使用分配的內(nèi)存塊
for (int i = 0; i < n; i++) {
dynamicArray[i] = i + 1;
}
// 當(dāng)不再需要分配的內(nèi)存時(shí),記得釋放它
free(dynamicArray);
dynamicArray = NULL;
}
return 0;
}
在調(diào)用free()
函數(shù)釋放動(dòng)態(tài)分配的內(nèi)存后,將指針dynamicArray
設(shè)置為NULL
是一個(gè)良好的習(xí)慣,但不是必須的。
設(shè)置指針為NULL
的優(yōu)點(diǎn):
- 避免懸掛指針(Dangling Pointer):如果在釋放內(nèi)存后不將指針設(shè)置為
NULL
,該指針將仍然保留先前的地址。如果你在后續(xù)代碼中繼續(xù)使用該指針,可能會(huì)導(dǎo)致懸掛指針,即指針指向的內(nèi)存已經(jīng)被釋放,這可能導(dǎo)致程序崩潰或產(chǎn)生難以調(diào)試的錯(cuò)誤。將指針設(shè)置為NULL
可以幫助你避免這種情況,因?yàn)槿绻麌L試使用空指針,程序?qū)a(chǎn)生明確的錯(cuò)誤(空指針解引用)。- 避免重復(fù)釋放:在釋放內(nèi)存后,如果將指針設(shè)置為
NULL
,你可以通過(guò)檢查指針是否為NULL
來(lái)確定是否已經(jīng)釋放了內(nèi)存。如果你在后續(xù)代碼中錯(cuò)誤地再次調(diào)用free()
,會(huì)導(dǎo)致未定義的行為。
如果你在后續(xù)代碼中小心地避免懸掛指針和重復(fù)釋放內(nèi)存,那么不設(shè)置為NULL
也不會(huì)導(dǎo)致問(wèn)題。然而,這是一個(gè)簡(jiǎn)單且有助于防范錯(cuò)誤的額外保護(hù)措施,所以建議在釋放內(nèi)存后將指針設(shè)置為NULL
。
1.2 calloc
calloc()
是另一個(gè)動(dòng)態(tài)內(nèi)存分配函數(shù),也屬于標(biāo)準(zhǔn)C庫(kù)(stdlib.h頭文件)。與malloc()
功能類(lèi)似,但在使用上有一些區(qū)別。
calloc()
函數(shù)的原型如下:
void* calloc (size_t num, size_t size);
其中num
是你想要分配的元素?cái)?shù)量,size
是每個(gè)元素的大?。ㄒ宰止?jié)為單位)。calloc()
函數(shù)會(huì)為num * size
字節(jié)的內(nèi)存塊分配空間,并將該內(nèi)存塊中的所有位初始化為零。
相對(duì)于malloc()
,calloc()
的一個(gè)優(yōu)勢(shì)是它會(huì)自動(dòng)初始化分配的內(nèi)存,這意味著你不需要手動(dòng)將分配的內(nèi)存清零。在某些情況下,這可能是非常有用的,特別是當(dāng)你需要確保分配的內(nèi)存一開(kāi)始就是零值時(shí)。
實(shí)例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int n = 5;
int* dynamicArray = (int*)calloc(n, sizeof(int));
if (dynamicArray == NULL) {
printf("內(nèi)存分配失敗!\n");
} else {
// 使用分配的內(nèi)存塊,這里的內(nèi)存已經(jīng)被初始化為零
for (int i = 0; i < n; i++) {
printf("%d ", dynamicArray[i]); // 輸出: 0 0 0 0 0
}
// 當(dāng)不再需要分配的內(nèi)存時(shí),記得釋放它
free(dynamicArray);
}
return 0;
}
總結(jié):
calloc = malloc+memset 初始化為0
1.3 realloc
realloc
是一個(gè)用于重新分配內(nèi)存塊大小的函數(shù)。具體而言,它可以用于更改之前通過(guò)malloc
或calloc
分配的內(nèi)存塊的大小。
realloc
函數(shù)的聲明如下:
void *realloc(void *ptr, size_t size);
參數(shù)說(shuō)明:
-
ptr
:指向之前已分配內(nèi)存塊的指針。如果ptr
為NULL,則realloc
的行為就相當(dāng)于malloc
,即分配一個(gè)新的內(nèi)存塊。 -
size
:新的內(nèi)存塊大小,以字節(jié)為單位。
realloc
的工作原理如下:
- 如果
ptr
為NULL,那么realloc
的行為就等同于malloc(size)
,它將分配一個(gè)新的大小為size
字節(jié)的內(nèi)存塊,并返回指向該內(nèi)存塊的指針。 - 如果
size
為0,且ptr
不為NULL,那么realloc
的行為就等同于free(ptr)
,即釋放掉之前分配的內(nèi)存塊,并返回NULL指針。 - 如果
ptr
不為NULL且size
不為0,realloc
將嘗試重新分配之前分配的內(nèi)存塊。可能發(fā)生以下幾種情況:- 如果之前分配的內(nèi)存塊大小大于或等于
size
,則不會(huì)分配新的內(nèi)存塊,而是簡(jiǎn)單地返回原始內(nèi)存塊的指針,不會(huì)改變?cè)瓋?nèi)存塊的內(nèi)容。 - 如果之前分配的內(nèi)存塊大小小于
size
,realloc
會(huì)嘗試將原始內(nèi)存塊擴(kuò)展到新的大小。這可能會(huì)**在原始內(nèi)存塊后面的可用內(nèi)存空間進(jìn)行擴(kuò)展,如果沒(méi)有足夠的連續(xù)空間來(lái)擴(kuò)展,則realloc
可能會(huì)在另一個(gè)地方重新分配一個(gè)新的內(nèi)存塊,并將原始內(nèi)容復(fù)制到新的內(nèi)存塊中。**這意味著realloc
有可能返回一個(gè)新的指針,而不是原始指針,所以在使用realloc
后,最好將返回的指針賦值給原來(lái)的指針。 - 如果
realloc
在新的內(nèi)存塊分配失敗時(shí),將返回NULL,并且之前分配的內(nèi)存塊仍然保持未更改。
- 如果之前分配的內(nèi)存塊大小大于或等于
使用realloc
時(shí),應(yīng)該特別注意以下幾點(diǎn):
- 如果
realloc
返回NULL,表示重新分配失敗,原來(lái)的指針仍然有效,為避免內(nèi)存泄漏,應(yīng)該保存原來(lái)的指針,并根據(jù)需要釋放之前的內(nèi)存塊。 - 當(dāng)使用
realloc
時(shí),最好不要直接修改原始指針,而是將realloc
的結(jié)果賦值給原始指針,以防止意外的內(nèi)存問(wèn)題。
實(shí)例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(40);
if (p == NULL)
return 1;
//使用
int i = 0;
for (i = 0; i < 10; i++) {
*(p + i) = i;
}
for (i = 0; i < 10; i++) {
printf("%d ", *(p + i));
}
//增加空間
// p = (int *)realloc(p, 80); //如果開(kāi)辟失敗的話,p變成了空指針,不能這么寫(xiě)
int *ptr = (int *) realloc(p, 80);
if (ptr != NULL) {
p = ptr;
ptr = NULL;
}
//當(dāng)realloc開(kāi)辟失敗的時(shí)候,返回的也是空指針
//使用
for (i = 10; i < 20; i++) {
*(p + i) = i;
}
for (i = 10; i < 20; i++) {
printf("%d ", *(p + i));
}
//釋放
free(p);
p = NULL;
return 0;
}
//輸出結(jié)果:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
2.常見(jiàn)的動(dòng)態(tài)內(nèi)存錯(cuò)誤
2.1 對(duì)NULL指針的解引用操作
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(20);
*p = 5; //錯(cuò)誤,空指針解引用
//為了不對(duì)空指針解引用 需要進(jìn)行判斷
if (p == NULL) {
perror("malloc");
return 1;
}
else {
*p = 5;
}
free(p);
p = NULL;
return 0;
}
2.2 對(duì)動(dòng)態(tài)開(kāi)辟空間的越界訪問(wèn)
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(20);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 20; i++)//越界訪問(wèn) 20個(gè)字節(jié) 只能訪問(wèn)5個(gè)整型
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
2.3 對(duì)非動(dòng)態(tài)開(kāi)辟內(nèi)存使用free釋放
#include <stdio.h>
#include <stdlib.h>
int main() {
int a = 10;
int* p = &a;
free(p);// ok?
return 0;
}
編譯器會(huì)直接報(bào)錯(cuò)
2.4 使用free釋放一塊動(dòng)態(tài)開(kāi)辟內(nèi)存的一部分
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *) malloc(40);
if (p = NULL)
return 1;
int i = 0;
for (i = 0; i < 5; i++) {
*p = i;
p++;
}
//釋放
//在釋放的時(shí)候,p指向的不再是動(dòng)態(tài)內(nèi)存空間的起始位置
free(p);// p不再指向動(dòng)態(tài)內(nèi)存的起始位置
p++;
return 0;
}
2.5 對(duì)同一塊動(dòng)態(tài)內(nèi)存多次釋放
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(40);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 5; i++) {
*(p + i) = i;
}
//重復(fù)free
free(p);
p = NULL;//如果將p賦值為NULL 就可以在free,否則編譯器會(huì)直接報(bào)錯(cuò)
free(p);
return 0;
}
2.6 動(dòng)態(tài)開(kāi)辟內(nèi)存忘記釋放(內(nèi)存泄漏)
#include <stdio.h>
#include <stdlib.h>
int *get_memory() {
int *p = (int *) malloc(40);
return p;
}
int main() {
int *ptr = get_memory();
//使用
//釋放 如果不釋放 就會(huì)導(dǎo)致內(nèi)存泄漏
free(ptr);
return 0;
}
3.C/C++程序的內(nèi)存開(kāi)辟
C/C++程序內(nèi)存分配的幾個(gè)區(qū)域:
- 棧區(qū)(stack):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。 棧區(qū)主要存放運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回?cái)?shù)據(jù)、返回地址等。
- 堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。分配方式類(lèi)似于鏈表。
- 數(shù)據(jù)段(靜態(tài)區(qū))(static)存放全局變量、靜態(tài)數(shù)據(jù)。程序結(jié)束后由系統(tǒng)釋放。
- 代碼段:存放函數(shù)體(類(lèi)成員函數(shù)和全局函數(shù))的二進(jìn)制代碼。
普通的局部變量是在棧區(qū)分配空間的,棧區(qū)的特點(diǎn)是在上面創(chuàng)建的變量出了作用域就銷(xiāo)毀。 但是被static修飾的變量存放在數(shù)據(jù)段(靜態(tài)區(qū)),數(shù)據(jù)段的特點(diǎn)是在上面創(chuàng)建的變量,直到程序結(jié)束才銷(xiāo)毀,所以生命周期變長(zhǎng)。
4.經(jīng)典筆試題
4.1 題目1
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char *p) {
p = (char *) malloc(100);
}
void Test(void) {
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main() {
Test();
return 0;
}
請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?
運(yùn)行Test
函數(shù)會(huì)導(dǎo)致未定義行為。
在GetMemory
函數(shù)中,傳入的char *p
是一個(gè)局部變量,當(dāng)在函數(shù)內(nèi)部對(duì)其進(jìn)行修改,并不會(huì)影響到原始調(diào)用函數(shù)中的指針。這是因?yàn)楹瘮?shù)的參數(shù)是通過(guò)值傳遞的,即函數(shù)得到的是實(shí)參的副本,對(duì)參數(shù)的修改不會(huì)影響原始的實(shí)參。
在Test
函數(shù)中,將一個(gè)NULL指針str
傳遞給GetMemory
函數(shù),然后在GetMemory
函數(shù)中分配了內(nèi)存并將新的地址賦給p
。但這對(duì)str
并沒(méi)有影響,str
仍然是一個(gè)NULL指針,指向未分配的內(nèi)存。
接著,在Test
函數(shù)中使用strcpy
將字符串拷貝到str
指向的內(nèi)存,但是str
指向的內(nèi)存并沒(méi)有被分配,這將導(dǎo)致未定義行為。
為了正確地分配內(nèi)存并使用指針,需要修改GetMemory
函數(shù),使其返回分配的內(nèi)存地址,并在Test
函數(shù)中接收返回的指針。另外,別忘了在使用完內(nèi)存后,需要使用free
函數(shù)來(lái)釋放動(dòng)態(tài)分配的內(nèi)存。
改寫(xiě)1:
#include <stdio.h>
#include <stdlib.h>
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;
}
int main() {
Test();
return 0;
}
改寫(xiě)2:
#include <stdio.h>
#include <stdlib.h>
char *GetMemory() {
char *p = (char *) malloc(100);
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory(); //接受返回的p
strcpy(str, "hello world");
printf(str);
//釋放
free(str);
str = NULL;
}
int main() {
Test();
return 0;
}
4.2 題目2
char *GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
printf(str);
}
請(qǐng)問(wèn)運(yùn)行Test函數(shù)會(huì)有什么樣的結(jié)果?
在GetMemory
函數(shù)中,定義了一個(gè)局部數(shù)組char p[] = "hello world";
,然后將該數(shù)組的地址返回給調(diào)用者。但是,一旦GetMemory
函數(shù)執(zhí)行完畢,其局部變量(p
數(shù)組)將被銷(xiāo)毀,因?yàn)樗且粋€(gè)自動(dòng)存儲(chǔ)類(lèi)別的局部變量。所以,返回的指針指向的是已經(jīng)無(wú)效的內(nèi)存。
在Test
函數(shù)中,你將GetMemory
的返回值賦給指針str
,然后使用printf
打印str
指向的內(nèi)容。由于GetMemory
返回的是一個(gè)無(wú)效的指針(指向已經(jīng)被銷(xiāo)毀的局部數(shù)組),printf
可能會(huì)打印出垃圾值,或者程序崩潰,或者導(dǎo)致其他不可預(yù)測(cè)的結(jié)果。
這個(gè)問(wèn)題被稱(chēng)為"懸掛指針"問(wèn)題,因?yàn)橹羔槕覓煸谥赶蛞呀?jīng)無(wú)效的內(nèi)存位置上。
要解決這個(gè)問(wèn)題,可以考慮使用動(dòng)態(tài)內(nèi)存分配來(lái)分配存儲(chǔ)字符串的內(nèi)存,并在使用完后記得使用free
來(lái)釋放內(nèi)存。
修改后的代碼示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *GetMemory(void) {
char *p = (char *)malloc(strlen("hello world") + 1);
if (p != NULL) {
strcpy(p, "hello world");
}
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
if (str != NULL) {
printf("%s\n", str);
free(str); // 釋放內(nèi)存
}
}
int main() {
Test();
return 0;
}
4.3 題目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);
}
請(qǐng)問(wèn)運(yùn)行Test函數(shù)會(huì)有什么樣的結(jié)果?
沒(méi)有釋放內(nèi)存,導(dǎo)致內(nèi)存泄漏
修改后的代碼實(shí)例:
void GetMemory(char **p, int num) {
*p = (char *)malloc(num);
}
void Test(void) {
char *str = NULL;
GetMemory(&str, 100);
if (str != NULL) {
strcpy(str, "hello");
printf("%s\n", str);
free(str); // 釋放內(nèi)存
}
}
4.4 題目4
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL) {
strcpy(str, "world");
printf(str);
}
}
請(qǐng)問(wèn)運(yùn)行Test 函數(shù)會(huì)有什么樣的結(jié)果?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-613272.html
str被提前釋放,再次訪問(wèn)str會(huì)導(dǎo)致野指針行為文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-613272.html
到了這里,關(guān)于<C語(yǔ)言> 動(dòng)態(tài)內(nèi)存管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!