目錄
- 1.動態內存函數的介紹
- 1.malloc
- 2.free
- 2.calloc
- 4.realloc
- 2.常見的動態內存錯誤
- 3.C/C++程序的內存開辟
- 4.柔性數組
- 1.是什么?
- 2.柔性數組的特點
- 3.柔性數組的使用
- 4.柔性數組的優勢
1.動態內存函數的介紹
1.malloc
- 函數原型:
void* malloc(size_t size)
- 功能:
malloc()
向內存申請一塊連續可用的空間,并返回指向這塊空間的指針
- 返回值:
- 如果開辟成功,則返回一個指向開辟好空間的指針
- 如果開辟失敗,則返回一個NULL指針
- 因此malloc的返回值一定要做檢查
- 返回值的類型是
void*
,所以**malloc()
并不知道開辟空間的類型**,具體在使用的時候使用者自己來決定(強制類型轉換) - 如果參數
size == 0
,malloc()
的行為是標準是未定義的,取決于編譯器
2.free
- 函數原型:
void free(void* ptr)
- 功能:
free()
用來釋放動態開辟的內存 - 如果參數
ptr
指向的空間不是動態開辟的,那free()
的行為是未定義的 - 如果參數
ptr
是NULL
,則函數什么事都不做 - 一次完整的動態內存開辟的例子
int* p = (int*)malloc(10 * sizeof(int));
if(p == NULL)
{perror("main"); // main: xxxxreturn 0;
}// 使用
for(int i = 0; i < 10; i++)
{*(p + i) = i;
}free(p); // 回收空間
p = NULL; // 手動把p置為NULL
2.calloc
- 函數原型:
void* calloc(size_t num, size_t size)
- 功能:為
num
個大小為size
的元素開辟一塊空間,并且把空間的每個字節初始化為0 - 與函數
malloc()
的區別只在于calloc()
會在返回地址之前把申請的空間的每個字節初始化為全0 - 如果對申請的內存空間的內容要求初始化,那么可以很方便的使用calloc函數來完成任務
4.realloc
-
函數原型:
void* realloc(void* ptr, size_t size)
-
功能:
realloc()
的出現讓動態內存管理更加靈活- 有時會發現過去申請的空間太小了,有時候又會覺得申請的空間過大了,一定會對內存的大小做靈活的調整,那
realloc()
就可以做到對動態開辟內存大小的調整
-
參數:
ptr
:要調整的內存地址- 若
ptr == NULL
,則realloc()
作用同malloc()
- 若
size
:調整之后新大小
-
返回值:調整之后的內存起始位置
-
注意:這個函數調整原內存空間大小的基礎上,還會將原來內存中的數據移動到新的空間
-
realloc()
在調整內存空間時,存在兩種情況- 原有空間之后有足夠大的空間
- 要擴展內存就直接原有內存之后直接追加空間,原來空間的數據不發生變化
- 原有空間之后沒有足夠大的空間
- 擴展方法:
- 在堆空間上另找一個合適大小的連續空間來使用
- 這樣函數返回的是一個新的內存地址
- 擴展方法:
- 由于上述的兩種情況,
realloc()
的使用就要注意一些
- 原有空間之后有足夠大的空間
-
例如:為了確保內存空間成功開辟,拿一個臨時指針去接收新開辟的空間的地址,再賦值,這樣保證了萬一沒有成功開辟新的地址,而丟失了原來的地址
int* p = (int*)calloc(10, sizeof(int));
if(p == NULL)
{perror("main");return 1;
}for(int i = 0; i < 10; i++) // 使用
{*(p + i) = i;
}// 這里需要p指向更大空間,realloc調整
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if(ptr != NULL)
{p = ptr;
}free(p);
p = NULL;
2.常見的動態內存錯誤
- 對NULL指針的解引用操作
- 對動態開辟空間的越界訪問
- 使用free釋放非動態開辟的空間
- 使用free釋放動態內存中的一部分
- 對同一塊動態開辟的空間,多次釋放
- 動態開辟的空間忘記釋放- 內存泄漏 -> 比較嚴重
- 經典例子一:
str
傳給GetMemory()
的時候是值傳遞,所以GetMemory()
的形參p
是str
的一份臨時拷貝- 在
GetMemory()
內部動態申請空間的地址,存放在p
中,不會影響外邊的str
,所以當GetMemory()
返回之后,str
依然是NULL
,所以strcpy()
會失敗 - 當
GetMemory()
返回之后,形參p
銷毀,使得動態開辟的100個字節存在內存泄漏無法釋放
void GetMemory(char* p)
{p = (char *)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");
}int main()
{Test();return 0;
}// 改進一
char* GetMemory(char* p)
{p = (char *)malloc(100);return p;
}void Test(void)
{char* str = NULL;str = GetMemory(str);strcpy(str, "SnowK");free(str);str = NULL:
}// 改進二
void GetMemory(char** p)
{*p = (char *)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "SnowK");free(str);str = NULL:
}
- 經典例子二:
GetMemory()
內部創建的數組是在棧區上創建的- 出了函數,
p[]
的空間就還給了操作系統 - 返回的地址是沒有實際的意義,如果通過返回的地址去訪問內存,就是非法訪問內存
char* GetMemory()
{char p[] = "SnowK";return p;
}void Test()
{char* str = NULL;str = GetMemory();
}
3.C/C++程序的內存開辟
- C/C++程序內存分配的幾個區域:
- 棧區(stack):
- 在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放
- 棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限
- 棧區主要存放運行函數而分配的局部變量、函數參數、返回數據、返回地址等
- 堆區(heap):
- 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收
- 分配方式類似于鏈表
- 數據段(靜態區)(static):
- 存放全局變量、靜態數據程序
- 結束后由系統釋放
- 代碼段:存放函數體(類成員函數和全局函數)的二進制代碼
- 實際上普通的局部變量是在棧區分配空間的,棧區的特點是在上面創建的變量出了作用域就銷毀
- 但是被
static
修飾的變量存放在數據段(靜態區),數據段的特點是在上面創建的變量,直到程序結束才銷毀,所以生命周期變長
- 棧區(stack):
4.柔性數組
1.是什么?
struct s
{int n;int arr[]; // 大小是未知
}
2.柔性數組的特點
- 結構中的柔性數組成員前面必須至少一個其他成員
sizeof
返回的這種結構大小不包括柔性數組的內存- 包含柔性數組成員的結構用
malloc()
函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小
typedef struct st_type
{int i;int arr[0]; // 柔性數組成員
}type_a;printf("%d\n", sizeof(type_a)); // 輸出的是4
3.柔性數組的使用
// 期望arr的大小是10個整形
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
ps->n = 10;for(int i = 0; i < 10; i++)
{ps->arr[i] = i;
}// 增加
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));
if(ptr != NULL)
{ps = ptr;
}// 使用
// ...
// 釋放
free(ps);
ps = NULL;
4.柔性數組的優勢
- 方便內存釋放
- 如果代碼是在一個給別人用的函數中,你在里面做了二次內存分配,并把整個結構體返回給用戶
- 用戶調用
free()
可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free()
,所以你不能指望用戶來發現這個事 - 所以,如果把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次
free()
就可以把所有的內存也給釋放掉
- 這樣有利于訪問速度
- 連續的內存有益于提高訪問速度,也有益于減少內存碎片
- 其實,個人覺得也沒高多少,反正也避免不了要用做偏移量的加法來尋址
- 連續的內存有益于提高訪問速度,也有益于減少內存碎片