c語言 進階 動態內存管理

動態內存管理

    • 1. 為什么存在動態內存分配
    • 2. 動態內存函數的介紹?
      • 2.1 malloc 和 free
        • malloc 函數
        • free 函數
      • 2.2內存泄漏
      • 2.3 calloc
      • 2.4 realloc
    • 3. 常見的動態內存錯誤
      • 3.1 對NULL指針的解引用操作
      • 3.2 對動態開辟空間的越界訪問
      • 3.3 對非動態開辟內存使用free釋放
        • 3.4 使用free釋放一塊動態開辟內存的一部分
      • 3.5 對同一塊動態內存多次釋放
      • 3.6 動態開辟內存忘記釋放(內存泄漏)
    • 4. 幾個經典的筆試題
        • 4.1 題分析
        • 4.2 題分析
        • 4.3 題分析
        • 4.4 題分析
    • 5. 柔性數組(Flexible Array)
        • 5.1 柔性數組的特點
        • 5.2 柔性數組的使用
        • 5.3 柔性數組的優勢

1. 為什么存在動態內存分配

  • 已掌握的內存開辟方式及局限:
    • 棧上開辟的空間,如int val = 20;是在棧上分配4個字節,char arr[10] = {0};是在棧上分配10個字節的連續空間。
    • 這些方式有明顯局限:
      1. 空間大小固定,比如char arr[10]只能開辟10個字節,無法根據程序運行時的需求改變大小,而且棧空間通常有限,不能開辟過大的空間。
      2. 數組在聲明時必須指定長度,像int n; scanf("%d", &n); char arr[n];這種在C99之前是不允許的,因為數組的長度需要在編譯時確定,而程序運行時才能知道的長度無法通過這種方式開辟空間。
    • 實際開發中,很多場景下空間大小只有在程序運行時才能確定,例如根據用戶輸入的數字來決定需要存儲多少個數據,這時候靜態開辟空間的方式就無法滿足需求,動態內存分配應運而生。

2. 動態內存函數的介紹?

2.1 malloc 和 free

malloc 函數
  • malloc函數
    • 函數原型void* malloc(size_t size);的作用是向內存的堆區申請一塊連續可用的空間。
      • 如果開辟成功,則返回一個指向開辟好空間的指針。
      • 如果開辟失敗,則返回一個NULL指針,因此malloc的返回值一定要做檢查
      • 返回值的類型是 void* ,所以malloc函數并不知道開辟空間的類型,具體在使用的時候使用者自己來決定。
      • size_t size
        表示要分配的內存塊的大小(以字節為單位)。
      • 如果參數size0malloc的行為是標準是未定義的,取決于編譯器
    • 示例1(正常開辟):
// 申請可以存儲5個int類型數據的空間,int占4字節,所以總共申請5*4=20字節
int* p = (int*)malloc(5 * sizeof(int));
// 必須檢查開辟是否成功,因為當內存不足時,malloc會返回NULL
if (p == NULL) {// 打印錯誤信息,perror會在字符串后加上具體的錯誤原因perror("malloc failed");return 1; // 開辟失敗,退出程序
}
// 成功開辟后使用空間,給每個元素賦值
for (int i = 0; i < 5; i++) {p[i] = i * 10;
}
  • 示例 2(開辟失敗):?
// 申請1000000000個int類型的空間,可能因內存不足導致失敗
int* p = (int*)malloc(1000000000 * sizeof(int));
if (p == NULL) {perror("malloc failed"); // 可能輸出"malloc failed: Not enough space"return 1;
}
  • 特性總結:?
    • 開辟成功返回指向該空間的指針,由于返回類型是void*,所以需要根據實際存儲的數據類型進行強制類型轉換,比如存儲int類型就轉為int*。?
    • 開辟失敗返回 NULL 指針,所以使用前必須檢查返回值是否為 NULL。?
    • size0 時,C 語言標準沒有定義其行為,不同的編譯器可能有不同的處理方式,有的可能返回 NULL,有的可能返回一塊很小的空間,實際開發中應避免這種情況。
free 函數

函數原型void free(void* ptr);專門用于釋放動態開辟的內存,將內存歸還給系統。

  • 示例:
int* p = (int*)malloc(5 * sizeof(int));
if (p == NULL) {perror("malloc failed");return 1;
}
// 使用空間...
free(p); // 釋放p指向的動態內存,此時這塊內存歸還給系統,不能再使用
p = NULL; // 釋放后將指針置為NULL,避免成為野指針,野指針指向的內存已無效,使用會導致不可預期的錯誤
  • 特性總結:?

    • 只能釋放動態開辟的內存,比如int a = 10; int* p = &a; free(p);這種釋放棧上空間的行為是未定義的,可能導致程序崩潰。?
    • ptr NULL 指針時,free 函數什么也不做,所以釋放后將指針置為NULL是安全的。
  • malloc和free都聲明在 stdlib.h 頭文件中

2.2內存泄漏

定義: 動態開辟的內存沒有通過 free 釋放,并且指向該內存的指針也丟失了,導致系統無法回收這塊內存,這就是內存泄漏。

  • 示例 1(忘記釋放):
void test() {int* p = (int*)malloc(100);// 使用p后沒有調用free(p),函數結束后p被銷毀,再也無法找到這塊內存,導致內存泄漏
}
int main() {test();// 程序運行期間,test函數中申請的100字節內存一直未被釋放return 0;
}
  • 示例 2(指針被修改導致無法釋放):
 int* p = (int*)malloc(100);
p++; // 指針指向了動態開辟空間的第二個字節,不再指向起始位置
free(p); // 錯誤,無法釋放,因為free需要指向動態開辟空間的起始地址,同時原起始地址丟失,導致內存泄漏
  • 危害:內存泄漏不會導致程序立即崩潰,但如果程序長期運行(如服務器程序、嵌入式程序),隨著時間的推移,泄漏的內存會越來越多,最終會耗盡系統內存,導致程序運行緩慢甚至崩潰。?
  • 預防:?
    • 動態內存使用完畢后,及時調用 free 函數釋放,并將指針置為 NULL。?
    • 在函數中申請的動態內存,要確保在函數返回前釋放,或者將指針傳遞出去由外部釋放。?
    • 避免在釋放內存前修改指針的指向,如果需要移動指針操作,先保存起始地址。

2.3 calloc

  • 函數原型void* calloc(size_t num, size_t size);,其功能是為num個大小為size的元素開辟一塊空間,并且會將這塊空間的每個字節都初始化為0。與函數 malloc 的區別只在于 calloc 會在返回地址之前把申請的空間的每個字節初始化為全0。
  • 示例(與malloc對比):
// 使用calloc申請3個int類型的空間
int* p1 = (int*)calloc(3, sizeof(int));
// 使用malloc申請3個int類型的空間
int* p2 = (int*)malloc(3 * sizeof(int));
if (p1 == NULL || p2 == NULL) {perror("malloc/calloc failed");return 1;
}
// 打印空間中的值
printf("calloc初始化后的值:");
for (int i = 0; i < 3; i++) {printf("%d ", p1[i]); // 輸出0 0 0,因為calloc會初始化
}
printf("\nmalloc初始化后的值:");
for (int i = 0; i < 3; i++) {printf("%d ", p2[i]); // 輸出隨機值,因為malloc不會初始化
}
// 釋放空間
free(p1);
p1 = NULL;
free(p2);
p2 = NULL;

輸出結果
在這里插入圖片描述

  • malloc 的區別:?
    • 參數不同:calloc 需要兩個參數,分別是元素的個數和每個元素的大小;malloc 只需要一個參數,即總共需要開辟的字節數。?
    • 初始化不同:calloc 會將申請的空間每個字節都初始化為 0;malloc 不會初始化,空間中的值是隨機的(取決于內存中之前存儲的數據)。?
  • 適用場景:當需要申請一塊初始化為 0 的動態內存時,使用 calloc 更方便,避免了使用 malloc 后再調用 memset 進行初始化的步驟。

2.4 realloc

  • 函數原型void* realloc(void* ptr, size_t size);,用于調整已經動態開辟的內存空間的大小,ptr是指向原來動態開辟空間的指針,size是調整后的新大小(以字節為單位)。
  • 調整內存的兩種情況:
    1. 原有空間之后有足夠的空閑空間:這種情況下,realloc會直接在原有空間的后面追加空間,不會移動原有數據,返回原來的指針。
    2. 原有空間之后沒有足夠的空閑空間:這種情況下,realloc會在堆區重新找一塊大小合適的空間,將原來空間中的數據復制到新空間,然后釋放原來的空間,返回新空間的指針。
  • 示例(正確使用):
// 先申請4個int的空間
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL) {perror("malloc failed");return 1;
}
// 給空間賦值
for (int i = 0; i < 4; i++) {p[i] = i;
}
// 現在需要將空間調整為8個int,用新指針接收realloc的返回值
int* new_p = (int*)realloc(p, 8 * sizeof(int));
if (new_p == NULL) {perror("realloc failed");// 如果realloc失敗,原來的p仍然有效,需要釋放,避免內存泄漏free(p);p = NULL;return 1;
}
// 調整成功,更新指針
p = new_p;
// 使用調整后的空間
for (int i = 4; i < 8; i++) {p[i] = i;
}
// 釋放空間
free(p);
p = NULL;

注意:最好別用要動態修改的指針來接受返回值因為若realloc失敗返回NULL,會導致指針變為NULL,原來的100字節內存無法釋放,造成內存泄漏

  • 錯誤示例(用原指針接收返回值):
 int* p = (int*)malloc(100);
// 錯誤,若realloc失敗返回NULL,會導致p變為NULL,原來的100字節內存無法釋放,造成內存泄漏
p = (int*)realloc(p, 200);

注意事項:?

  • realloc 的第一個參數為 NULL 時,其功能相當于 malloc,即realloc(NULL, size)等價于malloc(size)。?
  • 調整后的空間大小可以比原來小,此時會截斷原有數據,只保留前面部分數據。?
  • 使用 realloc 后,原來的指針可能會失效(當需要移動數據時),所以必須使用 realloc 的返回值來訪問調整后的空間。

3. 常見的動態內存錯誤

3.1 對NULL指針的解引用操作

  • 錯誤原因:當malloccallocrealloc函數開辟內存失敗時,會返回NULL指針,而NULL指針不指向任何有效的內存空間,對其進行解引用操作(如賦值、取值)會導致程序崩潰。
  • 示例(錯誤):
int* p = (int*)malloc(1000000000); // 申請過大空間,可能失敗返回NULL
*p = 10; // 對NULL指針解引用,程序會崩潰

避免方法: 在使用動態內存函數返回的指針之前,必須檢查該指針是否為 NULL。

  • 示例(正確):
 int* p = (int*)malloc(1000000000);
if (p == NULL) {perror("malloc failed"); // 打印錯誤信息return 1; // 不繼續使用指針,避免解引用NULL
}
*p = 10; // 指針非NULL,可安全使用

3.2 對動態開辟空間的越界訪問

  • 錯誤原因:訪問動態開辟的內存空間時,超出了申請的范圍,就像數組越界訪問一樣,會導致不可預期的錯誤,可能修改其他內存的數據,也可能導致程序崩潰。
  • 示例:
// 申請3個int的空間,共3*4=12字節,有效訪問范圍是p[0]到p[2]
int* p = (int*)malloc(3 * sizeof(int));
if (p == NULL) {perror("malloc failed");return 1;
}
// 循環訪問到了p[3]和p[4],超出了申請的空間范圍,屬于越界訪問
for (int i = 0; i < 5; i++) {p[i] = i; // i=3、4時越界
}
free(p);
p = NULL;
  • 危害:越界訪問可能會修改其他動態開辟的內存數據,或者破壞堆區的管理信息,導致后續的內存操作(如 free)出現錯誤。?
  • 避免方法:訪問動態開辟的空間時,嚴格控制訪問范圍,確保不超過申請的大小。比如申請了 n 個 int 類型的空間,訪問索引就只能在 0 到 n-1 之間。

3.3 對非動態開辟內存使用free釋放

  • 錯誤原因:free函數的作用是釋放動態開辟的內存(堆區的內存),而棧上的局部變量、全局變量等非動態開辟的內存,其生命周期由系統自動管理,不需要也不能用free釋放,對這些內存使用free會導致程序行為未定義,通常會引發程序崩潰。
  • 示例(錯誤):
int a = 10; // 棧上的局部變量
int* p = &a;
free(p); // 錯誤,釋放非動態開辟的內存,程序可能崩潰
p = NULL;

避免方法:明確區分動態開辟的內存和非動態開辟的內存,只對通過malloccallocrealloc函數申請的內存使用 free 釋放。

3.4 使用free釋放一塊動態開辟內存的一部分
  • 錯誤原因:free函數釋放動態內存時,要求指針必須指向動態開辟內存的起始地址,因為內存管理系統需要通過起始地址來回收整個內存塊。如果指針指向的是動態開辟內存的中間位置,free無法正確回收內存,會破壞堆區的內存管理結構,導致程序出錯。
  • 示例(錯誤):
int* p = (int*)malloc(4 * sizeof(int)); // p指向動態開辟內存的起始地址
if (p == NULL) {perror("malloc failed");return 1;
}
p++; // p現在指向動態開辟內存的第二個int的位置,不再是起始地址
free(p); // 錯誤,釋放的是內存的一部分,程序可能崩潰
  • 避免方法:在釋放動態內存之前,確保指針指向動態開辟內存的起始地址。如果在操作過程中移動了指針,需要先保存起始地址。?
  • 示例(正確):?
	 int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL) {perror("malloc failed");return 1;
}
int* q = p; // 保存起始地址
p++; // 移動指針進行操作
// ... 使用p進行操作
free(q); // 使用保存的起始地址釋放內存
q = NULL;
p = NULL;

3.5 對同一塊動態內存多次釋放

  • 錯誤原因:同一塊動態內存被free多次,會導致堆區內存管理結構被破壞,因為第一次釋放后,該內存已經歸還給系統,再次釋放時,系統無法識別該內存塊的狀態,從而引發程序崩潰。
  • 示例(錯誤):
int* p = (int*)malloc(100);
free(p);
free(p); // 錯誤,對同一塊內存多次釋放,程序可能崩潰
  • 避免方法:釋放內存后,立即將指針置為 NULL,因為 free 函數對 NULL 指針什么也不做,這樣即使不小心再次釋放,也不會出現錯誤。?
  • 示例(正確):
 int* p = (int*)malloc(100);
free(p);
p = NULL; // 釋放后將指針置為NULL
free(p); // 安全,free對NULL指針無操作

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

  • 錯誤原因:動態開辟的內存需要手動通過free釋放,如果使用完畢后沒有釋放,并且指向該內存的指針也丟失了(如指針超出作用域被銷毀),系統就無法回收這塊內存,導致內存泄漏。
  • 示例1(函數中忘記釋放):
void test() {int* p = (int*)malloc(100); // 在函數內部申請動態內存// 使用p進行操作,但沒有釋放
} // 函數結束,p被銷毀,無法再釋放申請的100字節內存,造成內存泄漏
int main() {test();// 程序運行期間,test函數申請的內存一直未被釋放return 0;
}
  • 示例 2(指針被覆蓋導致無法釋放):
  • 危害:對于短期運行的程序,內存泄漏可能不會有明顯影響,因為程序結束后操作系統會回收所有內存;但對于長期運行的程序(如服務器程序、后臺服務),內存泄漏會導致可用內存越來越少,最終程序會因內存不足而崩潰。?
  • 避免方法:?
    • 動態內存使用完畢后,及時調用 free 釋放,并將指針置為 NULL。?
    • 在函數中申請的動態內存,如果需要在函數外部使用,要將指針返回給外部,由外部負責釋放;如果不需要在外部使用,一定要在函數返回前釋放。?
    • 避免覆蓋指向動態內存的指針,如果需要重新賦值,先釋放原來的內存。

4. 幾個經典的筆試題

4.1 題分析
  • 代碼實現
void GetMemory(char* p) {p = (char*)malloc(100);  // 為形參p分配內存
}
void Test(void) {char* str = NULL;GetMemory(str);          // 傳遞str的值(NULL)strcpy(str, "hello world");  // 操作NULL指針printf(str);
}
  • 運行結果:程序崩潰。?
  • 原因詳解:?
    • 值傳遞的局限性:GetMemory函數的參數pstr的副本(值傳遞),p在函數內被賦值為malloc 返回的地址,但這不會改變str的值(str仍為 NULL)。?
    • NULL 指針解引用:strcpy(str, ...)試圖向NULL 指針指向的內存寫入數據,這是未定義行為,會導致程序崩潰。?
    • 內存泄漏隱患:GetMemory malloc 分配的內存地址僅存于p,函數結束后p被銷毀,該內存無法釋放,造成內存泄漏。
4.2 題分析
  • 代碼實現
char* GetMemory(void) {char p[] = "hello world";  // 局部數組,存于棧區return p;  // 返回局部數組的地址
}
void Test(void) {char* str = NULL;str = GetMemory();  // 接收已銷毀的局部數組地址printf(str);  // 訪問無效內存
}
  • 運行結果:打印隨機值或亂碼(行為未定義)。
  • 原因詳解:
    • 局部變量的生命周期:數組pGetMemory函數的局部變量,存儲在棧區,函數執行結束后,棧區內存被釋放,p的地址變為無效(野指針)。
    • 野指針訪問:str接收的是無效地址,此時訪問該地址的內存(printf(str)),讀取到的是棧區殘留的隨機數據,結果不可預期。
    • 關鍵結論:不要返回局部變量的地址,其指向的內存會隨函數結束而失效。
4.3 題分析
  • 代碼實現
void GetMemory(char**p, int num) {*p = (char*)malloc(num);  // 為二級指針指向的指針分配內存
}
void Test(void) {char* str = NULL;GetMemory(&str, 100);  // 傳遞str的地址(二級指針)strcpy(str, "hello");  // 向分配的內存寫入數據printf(str);  // 打印"hello"
}
  • 運行結果:正常打印 "hello",但存在內存泄漏。?
  • 原因詳解:?
    • 二級指針的作用:GetMemory的參數p&str(二級指針),*p就是str本身,因此*p = malloc(...)能正確為str分配內存(str指向堆區的 100 字節)。?
    • 內存泄漏問題:str指向的堆區內存未通過 free 釋放,程序結束前該內存一直被占用,造成內存泄漏(尤其在多次調用時)。?
    • 改進方案:使用后添加free(str); str = NULL;釋放內存。
4.4 題分析
  • 代碼實現
void Test(void) {char* str = (char*)malloc(100);  // 分配堆區內存strcpy(str, "hello");free(str);  // 釋放str指向的內存if (str != NULL) {  // str仍指向已釋放的內存(野指針)strcpy(str, "world");  // 向已釋放的內存寫入數據printf(str);  // 訪問無效內存}
}
  • 運行結果:可能打印 "world",也可能崩潰或打印亂碼(行為未定義)。?
  • 原因詳解:?
    • free 后的指針狀態:free(str)釋放了內存,但str的值并未改變(仍指向原地址),此時str成為野指針。?
    • 訪問已釋放內存:strcpy(str, "world")向已歸還給系統的內存寫入數據,這會破壞堆區管理結構,可能導致后續內存操作出錯(如再次 malloc 時崩潰)。?
    • 預防措施:釋放內存后應立即將指針置為 NULL,即free(str); str = NULL;,此時if (str != NULL)條件不成立,避免無效操作。

5. 柔性數組(Flexible Array)

  • 柔性數組是C99標準引入的特殊數組形式,僅能作為結構體的最后一個成員存在,其大小在結構體定義時無需指定(或指定為0),因此也被稱為“可變長數組成員”。
  • 定義示例及編譯器兼容性:
// 方式1:數組大小指定為0,早期C99支持此形式,部分編譯器(如GCC)兼容
typedef struct st_type {int len;          // 用于記錄柔性數組的實際長度int data[0];      // 柔性數組成員,必須位于結構體末尾
} type_a;// 方式2:不指定數組大小(空數組形式),是C99推薦寫法,兼容更多編譯器(如MSVC)
typedef struct st_type {int len;int data[];       // 柔性數組成員,同樣位于結構體末尾
} type_a;
  • 核心約束:柔性數組成員前面必須至少有一個其他類型的成員(如示例中的int len),且不能是結構體的唯一成員。這是因為柔性數組本身不占用結構體的固定內存,需要通過前面的成員確定其起始偏移量。
5.1 柔性數組的特點
  1. 結構成員的位置約束
    • 柔性數組成員必須是結構體的最后一個成員,不能有其他成員跟在其后。
    • 錯誤示例(柔性數組后有其他成員):
typedef struct wrong_st {int a;int flex[];  // 柔性數組int b;       // 錯誤:柔性數組后不能有其他成員
} wrong_type;  // 編譯器會報錯
  1. sizeof運算符的計算規則:?
    • sizeof計算包含柔性數組的結構體大小時,僅計算柔性數組前面所有成員的總大小,完全忽略柔性數組的存在。?
    • 示例(基于type_a):
	// type_a中僅int len一個非柔性成員,占4字節
printf("sizeof(type_a) = %zu\n", sizeof(type_a));  // 輸出4,不包含data[]的大小
  • 原理:柔性數組的大小在編譯期未知,無法納入結構體的固定大小計算,其內存需在運行時動態分配。
  1. 內存分配的強制性與計算方式:?
    • 包含柔性數組的結構體必須通過動態內存分配函數(malloc/calloc/realloc)創建實例,不能在棧上直接定義變量(如type_a obj;是錯誤的,因為無法確定柔性數組的大小)。?
    • 分配內存時,總大小計算公式為:結構體固定大小(sizeof(type_a)) + 柔性數組實際所需字節數。?
    • 示例(為柔性數組分配 10 個int元素的空間):
	// 計算總大小:4(len) + 10*4(data)= 44字節
type_a* p = (type_a*)malloc(sizeof(type_a) + 10 * sizeof(int));
if (p == NULL) {perror("malloc failed");exit(EXIT_FAILURE);
}
p->len = 10;  // 記錄柔性數組的實際長度,方便后續訪問
5.2 柔性數組的使用
  • 基本使用流程:動態分配內存→初始化成員→訪問柔性數組→釋放內存。
    • 完整示例:
#include <stdio.h>
#include <stdlib.h>typedef struct st_type {int len;   // 記錄柔性數組元素個數int data[]; // 柔性數組成員
} type_a;int main() {// 1. 分配內存:結構體固定大小(4字節) + 5個int(20字節)= 24字節type_a* p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));if (p == NULL) {perror("malloc failed");return 1;}// 2. 初始化:設置柔性數組長度并賦值p->len = 5;for (int i = 0; i < p->len; i++) {p->data[i] = i * 10;  // 直接通過結構體指針訪問柔性數組}// 3. 訪問柔性數組元素printf("柔性數組元素:");for (int i = 0; i < p->len; i++) {printf("%d ", p->data[i]);  // 輸出:0 10 20 30 40}printf("\n");// 4. 釋放內存(一次free即可)free(p);p = NULL;  // 避免野指針return 0;
}
  • 柔性數組的動態調整(體現 “柔性”):?
    • 通過 realloc 函數可以隨時調整柔性數組的大小,原數據會自動遷移到新空間(若空間地址改變)。?
    • 示例(將上述示例中的柔性數組從 5 個int擴展到 8 個):
// 原p指向24字節空間,擴展為:4 + 8*4 = 36字節
type_a* new_p = (type_a*)realloc(p, sizeof(type_a) + 8 * sizeof(int));
if (new_p == NULL) {perror("realloc failed");free(p);  // 若擴展失敗,釋放原有內存return 1;
}
p = new_p;
p->len = 8;  // 更新長度記錄// 為新增的3個元素賦值
for (int i = 5; i < p->len; i++) {p->data[i] = i * 10;
}// 驗證擴展后的數據
printf("擴展后元素:");
for (int i = 0; i < p->len; i++) {printf("%d ", p->data[i]);  // 輸出:0 10 20 30 40 50 60 70
}free(p);
p = NULL;
  • 注意:調整大小時,realloc的第二個參數必須重新計算(sizeof(type_a) + 新元素個數*元素大小),不能直接基于原有柔性數組的長度累加。
5.3 柔性數組的優勢

以“存儲一段動態長度的整數序列”為例,對比柔性數組與“結構體+指針”兩種實現方式,凸顯柔性數組的優勢:

  • 實現方式對比
    • 柔性數組方式(type_a):
// 分配:一次malloc完成所有內存申請
type_a* fa = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
fa->len = 100;// 使用:直接通過fa->data[i]訪問
for (int i = 0; i < fa->len; i++) {fa->data[i] = i;
}// 釋放:一次free即可
free(fa);
fa = NULL;
  • 結構體 + 指針方式(type_b)
 typedef struct ptr_type {int len;int* data;  // 用指針指向動態數組
} type_b;// 分配:需兩次malloc,分別申請結構體和數組內存
type_b* pb = (type_b*)malloc(sizeof(type_b));
pb->len = 100;
pb->data = (int*)malloc(pb->len * sizeof(int));  // 二次分配// 使用:通過pb->data[i]訪問
for (int i = 0; i < pb->len; i++) {pb->data[i] = i;
}// 釋放:需兩次free,且必須先釋放數組,再釋放結構體
free(pb->data);  // 若忘記釋放,會導致內存泄漏
pb->data = NULL;
free(pb);
pb = NULL;:?
  • 優勢 1:內存釋放的簡潔性與安全性?
    • 柔性數組只需一次free操作,無需關注內部成員的內存管理,尤其在函數返回動態結構體時,能避免用戶因忘記釋放成員內存(如type_b中的data)而導致的內存泄漏。?
    • 示例(函數返回場景):
// 返回柔性數組結構體,用戶只需一次釋放
type_a* create_flex_array(int n) {type_a* p = (type_a*)malloc(sizeof(type_a) + n * sizeof(int));p->len = n;return p;
}// 用戶使用
type_a* arr = create_flex_array(50);
// ... 使用后
free(arr);  // 簡單安全,無內存泄漏風險
  • 優勢 2:內存連續性與訪問效率?
    • 柔性數組的所有內存(結構體固定部分 + 柔性數組部分)是連續的,存儲在同一塊內存區域中。這種連續性帶來兩個好處:?
      • 減少 CPU 緩存失效:連續內存更可能被一次性加載到 CPU 緩存中,訪問時無需頻繁從內存中讀取,速度更快。?
      • 簡化地址計算:訪問fa->data[i]時,編譯器只需通過fa的地址 +sizeof(int)(len的大小)即可定位到data的起始地址,再加上i*sizeof(int)得到目標元素地址,僅需一次地址計算。?
    • 結構體 + 指針方式中,結構體與數組內存是離散的,訪問pb->data[i]時,需先從pb中讀取data指針的地址,再計算i對應的偏移量,涉及兩次地址計算,且離散內存更難被 CPU 緩存優化。?
  • 優勢 3:減少內存碎片?
    • 內存碎片指系統中存在大量零散的、無法被有效利用的小內存塊。柔性數組通過一次內存分配獲取所有所需空間,相比兩次分配(結構體 + 指針)能減少內存碎片的產生,尤其在頻繁創建和銷毀動態數組時,效果更明顯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/915453.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/915453.shtml
英文地址,請注明出處:http://en.pswp.cn/news/915453.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Redis的五大基本數據類型

一、Redis基本知識與Redis鍵&#xff08;key&#xff09;常用操作命令。redis的默認端口6379。mysql默認端口號3306。 默認16個數據庫&#xff0c;類似數組的下標從0開始&#xff0c;初始默認使用0號庫。可以使用select index來切換數據庫&#xff0c;如&#xff1a;select 1&a…

達夢數據庫JSON_TABLE使用說明

在達夢數據庫&#xff08;DM Database&#xff09;中&#xff0c;將 JSON 數據轉換為表格形式可以使用內置的 JSON_TABLE 函數。以下是詳細步驟和示例&#xff1a;1. 核心函數&#xff1a;JSON_TABLE JSON_TABLE 用于將 JSON 數據解析為關系表結構&#xff0c;支持從 JSON 對象…

A316-1926-V1 USB多路高清音頻解碼器模組技術解析

隨著數字音頻技術的不斷發展&#xff0c;高品質音頻解決方案的需求日益增長。本文將介紹一款基于XMOS技術的高性能USB音頻解碼器模組——A316-1926-V1&#xff0c;這是一款專為高清音頻應用設計的專業模組。核心技術與特性A316-1926-V1是一款集成了多項先進技術的USB多路高清音…

.NET 8 中的 KeyedService

.NET 8 中的 KeyedService&#xff1a;新特性解析與使用示例 一、引言 在 .NET 8 的 Preview 7 版本中&#xff0c;引入了 KeyedService 支持。這一特性為開發者提供了按名稱&#xff08;name&#xff09;獲取服務的便利&#xff0c;在某些場景下&#xff0c;開發者無需再自行…

Paimon對比基于消息隊列(如Kafka)的傳統實時數倉方案的優勢

弊端&#xff1a;數據重復 -> 優勢&#xff1a;Paimon 主鍵表原生去重原方案弊端 (Kafka)問題: 消息隊列&#xff08;Kafka&#xff09;是僅支持追加&#xff08;Append-Only&#xff09;的日志流。當 Flink 作業發生故障恢復&#xff08;Failover&#xff09;或業務邏輯迭代…

Linux Shell 命令 + 項目場景

shell 命令1. 基礎文件操作命令1.1 ls - 列出目錄內容1.2 find - 文件搜索2. 版本控制命令2.1 git - 版本控制系統2.2 高級 Git 操作3. 文本搜索命令3.1 grep - 文本搜索3.2 高級搜索技巧4. Android 構建系統命令4.1 source - 加載環境變量4.2 lunch - 選擇構建目標4.3 m - And…

A316-Mini-V1:超小尺寸USB高清音頻解碼器模組技術探析

引言 隨著便攜式音頻設備的普及&#xff0c;對小型化、高性能音頻解決方案的需求日益增長。本文將介紹一款極致小型化的高性能USB音頻解碼器模組——A316-Mini-V1&#xff0c;這是一款基于XMOS XU316芯片的微型音頻處理模組。產品概述 A316-Mini-V1是一款專為小尺寸產品設計的M…

低代碼平臺買saas好還是私有化好

選擇低代碼平臺采用SaaS還是私有化部署&#xff0c;應根據企業具體情況考慮安全性、成本控制、維護難度、擴展需求等因素。 其中&#xff0c;安全性是決定企業選擇的重要因素之一。私有化部署意味著企業能夠完全掌控數據和系統的安全管理&#xff0c;更適合對數據安全要求極高的…

基于SkyWalking的微服務APM監控實戰指南

基于SkyWalking的微服務APM監控實戰指南 1. 業務場景描述 隨著微服務在生產環境中大規模應用&#xff0c;系統鏈路復雜、實例彈性伸縮、灰度發布等特點都給性能監控和問題診斷帶來了新的挑戰。傳統的單機或輕量級監控方案已無法滿足微服務環境下的全鏈路、分布式追蹤和實時告警…

Python 進階(五): Excel 基本操作

目錄 1. 概述2. 寫入 2.1 使用 xlwt2.2 使用 XlsxWriter 3. 讀取4. 修改 1. 概述 在現實中&#xff0c;很多工作都需要與數據打交道&#xff0c;Excel 作為常用的數據處理工具&#xff0c;一直備受人們的青睞&#xff0c;而大部分人都是手動操作 Excel&#xff0c;如果數據量…

32、鴻蒙Harmony Next開發:使用動畫-動畫概述

???屬性動畫轉場動畫粒子動畫組件動畫動畫曲線動畫銜接動畫效果幀動畫&#xff08;ohos.animator&#xff09; UI&#xff08;用戶界面&#xff09;中包含開發者與設備進行交互時所看到的各種組件&#xff08;如時間、壁紙等&#xff09;。屬性作為接口&#xff0c;用于控制…

【STM32】485接口原理

485 通信實驗 這篇文章是對 RS485通信 的原理、硬件連接、接口芯片&#xff08;SP3485&#xff09;、總線結構等都有詳盡的說明。我們在此處進行清晰有條理的講解整理&#xff0c;便于學習和實驗操作。 在了解485接口通信原理之前&#xff0c;我們先復習一下串口&#xff1a;串…

亞馬遜二審攻防全攻略:預防、應對與長效合規之道

當店鋪收到二審通知&#xff0c;不少賣家會陷入焦慮與慌亂&#xff0c;只要掌握科學的預防策略與應對方法&#xff0c;不僅能降低二審風險&#xff0c;即便遭遇審核也能順利突圍。一、未雨綢繆&#xff1a;預防二審的四大核心策略夯實資料真實性根基資料的真實性與一致性是亞馬…

添加狀態信息

1首先在數據字典里加入可借閱和不可借閱狀態2導入數據字典export default {name: "Book",dicts: [book_borrow_status],//導入數據字典data() {return {formData: {name: null,author: null,num: null,price: null,typeId: null,status:null//新加狀態屬性},3設置狀態…

234、回文鏈表

題目&#xff1a;解答&#xff1a;對143稍作修改即可&#xff0c;判斷兩個指針指向的是否一直相等。終止條件為不等或者head2nullptrclass Solution { public:ListNode *rev(ListNode *head){ListNode *cur head;ListNode *pre nullptr;while(cur){ListNode * nxt cur->n…

第15次:商品搜索

實現用戶在頁面可自由搜索某個商品的功能。 第1步&#xff1a;準備搜索功能用到的庫 pip install whoosh pip install jieba pip install django-haystackwhoosh是搜索引擎&#xff0c;對英文支持較好&#xff0c;但對中文效果不佳。jieba為中文分詞庫&#xff0c;彌補whoosh…

《使用Qt Quick從零構建AI螺絲瑕疵檢測系統》——0. 博客系列大綱

目錄【《使用Qt Quick從零構建AI螺絲瑕疵檢測系統》系列簡介】第一部分&#xff1a;基礎入門與項目啟航第二部分&#xff1a;核心視覺算法開發第三部分&#xff1a;模擬完整工業流程第四部分&#xff1a;軟件打包與高級特性【《使用Qt Quick從零構建AI螺絲瑕疵檢測系統》系列簡…

【Python】Python中的循環語句

循環語句導讀一、基本概念1.1 循環語句的執行流程1.2 循環語句的分類二、while語句三、for語句四、break與continue五、死循環六、循環中的else語句七、range()函數結語導讀 大家好&#xff0c;很高興又和大家見面啦&#xff01;&#xff01;&#xff01; 在上一篇內容中我們…

docker|Linux|以centos基礎鏡像為基礎制作nmap專用鏡像(鏡像瘦身計劃)

一、 最近由于某些場景下需要使用nmap&#xff0c;而nmap的rpm安裝包在源目標機器上使用有軟件沖突&#xff0c;因此&#xff0c;計劃使用docker部署nmap 具體計劃為 1、使用centos的基礎鏡像&#xff0c;在有網環境下&#xff0c;通過配置阿里云的yum倉庫&#xff0c;在cen…

基于單片機公交車報站系統/報站器

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽??????? 概述 公交車自動報站系統利用單片機作為核心控制器&#xff0c;結合GPS/北斗定位模塊、語音存…