🤡博客主頁:醉竺
🥰本文專欄:《C語言深度解剖》
😻歡迎關注:感謝大家的點贊評論+關注,祝您學有所成!
??💜💛想要學習更多C語言深度解剖點擊專欄鏈接查看💛💜???
目錄
1. 為什么存在動態內存分配
2. 動態內存函數?
2.1 malloc和free?
2.2 calloc?
2.3 realloc?
3. 常見的動態內存錯誤?
3.1 對NULL指針的解引用操作
3.2 對動態開辟空間的越界訪問?
3.3 對非動態開辟內存使用free釋放?
3.4 使用free釋放一塊動態開辟內存的一部分?
3.5 對同一塊動態內存多次釋放?
3.6 動態開辟內存忘記釋放(內存泄漏)?
4. 幾個經典的筆試題?
5. C/C++程序的內存開辟?
6. 柔性數組
6.1 柔性數組的特性:?
6.2 柔性數組的使用?
6.3 柔性數組的優勢?
1. 為什么存在動態內存分配
我們已經掌握的內存開辟方式有:
int val = 20;//在棧空間上開辟四個字節
char arr[10] = { 0 };//在棧空間上開辟10個字節的連續空間
但是上述的開辟空間的方式有兩個特點:
- 空間開辟大小是固定的。
- 數組在申明的時候,必須指定數組的長度,它所需要的內存在編譯時分配。
但是對于空間的需求,不僅僅是上述的情況。有時候我們需要的空間大小在程序運行的時候才能知道, 那數組的編譯時開辟空間的方式就不能滿足了。 這時候就只能動態內存開辟了。?
2. 動態內存函數?
2.1 malloc和free?
void* malloc (size_t size);
這個函數向內存申請一塊連續可用的空間,并返回指向這塊空間的指針。?
- 如果開辟成功,則返回一個指向開辟好空間的指針。
- 如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查。
- 返回值的類型是 void* ,所以malloc函數并不知道開辟空間的類型,具體在使用的時候使用者自己 來決定。
- 如果參數 size 為0,malloc的行為是標準是未定義的,取決于編譯器。?
C語言提供了另外一個函數free,專門是用來做動態內存的釋放和回收的,函數原型如下:?
void free (void* ptr);
free函數用來釋放動態開辟的內存。
- 如果參數 ptr 指向的空間不是動態開辟的,那free函數的行為是未定義的。
- 如果參數 ptr 是NULL指針,則函數什么事都不做。
malloc和free都聲明在 stdlib.h 頭文件中。
舉個例子:?
- 在釋放內存之后將指針置為 NULL 是一個良好的編程習慣,但并不是強制性的。然而,這個做法可以提供額外的保護,防止懸空指針(dangling pointer)的問題。
- 懸空指針是指向已經釋放的內存的指針。如果指針在釋放后沒有被置為 NULL,那么它仍然包含一個地址,這個地址可能不再有效,因為釋放的內存可能已經被重新分配給其他用途。如果懸空指針被錯誤地解引用,可能會導致未定義行為,包括程序崩潰、數據損壞或安全漏洞。
- 將指針置為 NULL 可以防止懸空指針被解引用,因為解引用 NULL 指針通常會導致程序立即崩潰,這樣開發者可以立即發現問題并修復它,而不是面對更難以追蹤的未定義行為。
2.2 calloc?
C語言還提供了一個函數叫 calloc , calloc 函數也用來動態內存分配。原型如下:?
void *calloc(size_t num, size_t size);
- num:要分配的內存塊的數量。
- size:每個內存塊的大小,以字節為單位。
- 函數的功能是為 num 個大小為 size 的元素開辟一塊空間,并且把空間的每個字節初始化為0。
- 與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節全初始化為0。?
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){//使用空間}free(p);p = NULL;return 0;
}
所以如何我們對申請的內存空間的內容要求初始化,那么可以很方便的使用calloc函數來完成任務。
2.3 realloc?
- realloc函數的出現讓動態內存管理更加靈活。
- 有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時 候內存,我們一定會對內存的大小做靈活的調整。那 realloc 函數就可以做到對動態開辟內存大小 的調整。
函數原型如下:
void* realloc (void* ptr, size_t size);
- ptr 參數是一個指向之前分配的內存塊的指針。
- size 參數指定了新的內存塊大小,以字節為單位。
- 返回值:realloc 函數嘗試改變 ptr 指向的內存塊的大小為 size 字節。如果重新分配成功,它返回一個指向新分配內存塊的指針,這個指針可能與 ptr 相同(如果原地擴大成功),也可能不同(如果需要移動數據到新的位置)。如果失敗,它返回 NULL,并且原始內存塊保持不變?,不會釋放。
realloc在調整內存空間(下面演示的是擴大空間)時存在兩種情況:
情況1:原有空間之后有足夠大的空間
情況2:原有空間之后沒有足夠大的空間?
情況1:當是情況1的時候,擴展內存是在原有內存之后直接追加空間,realloc 函數重新申請空間之后,返回的地址還是原始空間的起始地址,?原來空間的數據部沒發生變化。如果新大小大于舊大小,超出舊大小部分的數據是未初始化的。
情況2:當是情況2的時候,原有空間之后沒有足夠的空間擴容。此時擴展的方法:在堆空間上另外找一個滿足大小的連續空間,并把舊空間的數據拷貝到新空間,然后釋放舊的空間,返回新空間的起始地址。
#include <stdio.h>
#include <stdlib.h>
int main() {int* ptr;int old_size = 5;int new_size = 10;// 分配一個整數數組的空間ptr = (int*)malloc(old_size * sizeof(int));if (ptr == NULL) {fprintf(stderr, "Initial memory allocation failed\n");return 1;}// 使用分配的內存for (int i = 0; i < old_size; i++) {ptr[i] = i;}// 重新分配內存以增加數組大小int* new_ptr = (int*)realloc(ptr, new_size * sizeof(int));if (new_ptr == NULL) {fprintf(stderr, "Memory reallocation failed\n");free(ptr); // 釋放原有內存return 1;}// 更新指針并使用新分配的內存ptr = new_ptr;for (int i = old_size; i < new_size; i++) {ptr[i] = i;}// 打印數組元素for (int i = 0; i < new_size; i++) {printf("%d ", ptr[i]);}printf("\n");// 釋放內存free(ptr);return 0;
}
3. 常見的動態內存錯誤?
3.1 對NULL指針的解引用操作
3.2 對動態開辟空間的越界訪問?
3.3 對非動態開辟內存使用free釋放?
3.4 使用free釋放一塊動態開辟內存的一部分?
3.5 對同一塊動態內存多次釋放?
3.6 動態開辟內存忘記釋放(內存泄漏)?
忘記釋放不再使用的動態開辟的空間會造成內存泄漏。
切記: 動態開辟的空間一定要釋放,并且正確釋放 。?
4. 幾個經典的筆試題?
4.1 題目1:?
請問運行Test 函數會有什么樣的結果???
4.2 題目2:?
請問運行Test 函數會有什么樣的結果??
4.3 題目3:?
請問運行Test 函數會有什么樣的結果??
4.4 題目4:?
請問運行Test 函數會有什么樣的結果??
5. C/C++程序的內存開辟?
C/C++程序內存分配的幾個區域:?
1. 棧區(stack):在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結 束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是 分配的內存容量有限。 棧區主要存放運行函數而分配的局部變量、函數參數、返回數據、返 回地址等。
2. 堆區(heap):一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。分 配方式類似于鏈表。
3. 數據段(靜態區):(static)存放全局變量、靜態數據。程序結束后由系統釋放。
4. 代碼段:存放函數體(類成員函數和全局函數)的二進制代碼。?
有了這幅圖,我們就可以更好的理解在 static關鍵字修飾局部變量的例子了。
實際上普通的局部變量是在棧區分配空間的,棧區的特點是在上面創建的變量出了作用域就銷毀。 但是被static修飾的變量存放在數據段(靜態區),數據段的特點是在上面創建的變量,直到程序 結束才銷毀 所以生命周期變長。?
6. 柔性數組
C語言中的柔性數組(Flexible Array Member, FAM)是C99標準引入的一個特性,它允許結構體的最后一個成員是一個大小未指定的數組,這為處理可變長度數據結構提供了一種高效而靈活的方式。?
例如:
有些編譯器會報錯無法編譯可以改成:
6.1 柔性數組的特性:?
- 位置要求:柔性數組必須是結構體的最后一個成員。這是因為它不占用結構體本身的空間,而是緊隨結構體之后分配內存。
- 內存分配:雖然聲明時數組長度為0(如int a[0]或直接int a[]),但實際使用時,會通過動態內存分配為它分配足夠的空間以容納實際需要的元素數量。
- 內存對齊:柔性數組不會影響結構體的自然對齊,因此在計算結構體大小時,柔性數組不計入。這有助于高效地利用內存。
- 便捷的內存管理:由于柔性數組與結構體主體連續存儲,只需要一次內存分配即可為結構體和其后的柔性數組分配空間,這簡化了內存管理,尤其是在需要同時釋放結構體和數組時。
- 優化訪問速度:連續的內存布局有利于CPU緩存,提高數據訪問效率。
例如:
6.2 柔性數組的使用?
這樣柔性數組成員a,相當于獲得了100個整型元素的連續空間。?
6.3 柔性數組的優勢?
上述的 type_a 結構也可以設計為:?
上述 代碼1 和 代碼2 可以完成同樣的功能,但是更推薦?方法1?,方法1的好處:?
擴展閱讀:
C語言結構體里的數組和指針