【C語言】C語言動態內存管理

前言

在C語言編程中,內存管理一直是程序員需要重點關注的領域。動態內存管理更是如此,它不僅涉及到內存的靈活分配和釋放,還隱藏著許多潛在的陷阱。本文將從動態內存分配的基礎講起,逐步深入到常見的錯誤、經典筆試題分析,以及柔性數組的應用,幫助你全面掌握C語言動態內存管理的精髓。

一、為什么需要動態內存分配

在學習C語言的過程中,我們已經熟悉了棧空間的內存分配方式。例如:

int val = 20; // 在棧空間上開辟4個字節
char arr[10] = {0}; // 在棧空間上開辟10個字節的連續空間

這種方式雖然簡單,但有兩個明顯的局限性:
空間大小固定:數組的大小在編譯時必須確定,一旦確定就無法調整。
靈活性不足:在某些情況下,我們無法在編譯時確定需要的內存大小,例如需要根據用戶輸入動態分配內存。
為了解決這些問題,C語言引入了動態內存分配。通過動態內存分配,程序員可以在程序運行時根據實際需求申請和釋放內存,極大地提高了內存使用的靈活性。

二、malloc和free

2.1 malloc
malloc是C語言中用于動態內存分配的函數,其原型如下:

void* malloc(size_t size);

在這里插入圖片描述

功能:向內存申請一塊連續可用的空間,并返回指向這塊空間的指針。
返回值:
如果申請成功,返回一個指向開辟空間的指針。
如果申請失敗,返回NULL。因此,使用malloc時,必須檢查返回值是否為NULL。
返回值類型:void*,表示malloc函數并不知道開辟空間的具體類型,使用者需要自行決定。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int num = 0;scanf("%d", &num); // 用戶輸入需要分配的整數個數int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int)); // 動態分配內存if (NULL != ptr) // 檢查是否分配成功{int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0; // 初始化內存}}free(ptr); // 釋放內存ptr = NULL; // 將指針置為NULL,避免野指針return 0;
}

在這里插入圖片描述

2.2 free
free函數用于釋放動態分配的內存,其原型如下:

void free(void* ptr);

功能:釋放由malloc、calloc或realloc分配的內存。
注意事項:
如果ptr指向的內存不是動態分配的,free的行為是未定義的。
如果ptr為NULL,free不會執行任何操作。
malloc和free都聲明在stdlib.h頭文件中。

三、calloc和realloc

3.1 calloc
calloc也是C語言中用于動態內存分配的函數,其原型如下:

void* calloc(size_t num, size_t size);

功能:為num個大小為size的元素分配內存,并將內存中的每個字節初始化為0。
與malloc的區別:calloc會在返回地址之前將申請的空間的每個字節初始化為0。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)calloc(10, sizeof(int)); // 分配10個整數空間,并初始化為0if (NULL != p){int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i)); // 輸出初始化后的值}}free(p); // 釋放內存p = NULL; // 避免野指針return 0;
}

在這里插入圖片描述

輸出結果為:
0 0 0 0 0 0 0 0 0 0
3.2 realloc
realloc函數用于調整動態分配的內存大小,其原型如下:

void* realloc(void* ptr, size_t size);

在這里插入圖片描述

功能:調整由ptr指向的內存塊的大小為size。
注意事項:
如果ptr為NULL,realloc的行為等同于malloc(size)。
如果size為0,realloc的行為等同于free(ptr)。
如果調整成功,返回調整后的內存塊的指針;如果失敗,返回NULL,并且原內存塊保持不變。
realloc在調整內存大小時,會根據內存空間的可用性選擇以下兩種情況之一:
原有空間之后有足夠的空間:直接在原有內存之后追加空間,數據保持不變。
原有空間之后沒有足夠的空間:在堆空間上另找一個合適大小的連續空間,將原數據復制到新空間,并返回新的內存地址。
例如:

#include <stdio.h>
#include <stdlib.h>int main()
{int* ptr = (int*)malloc(100); // 初始分配100字節if (ptr != NULL){// 業務處理}else{return 1;}// 擴展容量int* p = NULL;p = (int*)realloc(ptr, 1000); // 調整為1000字節if (p != NULL){ptr = p; // 更新指針}// 業務處理free(ptr); // 釋放內存return 0;
}

四、常見的動態內存錯誤

4.1 對NULL指針的解引用操作

void test()
{int* p = (int*)malloc(INT_MAX / 4); // 可能分配失敗*p = 20; // 如果p為NULL,會導致程序崩潰free(p);
}

解決方法:在使用指針之前,必須檢查其是否為NULL。
4.2 對動態開辟空間的越界訪問

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int)); // 分配10個整數空間if (NULL == p){perror("malloc")return 1;}for (i = 0; i <= 10; i++) // 越界訪問{*(p + i) = i;}free(p);
}

解決方法:嚴格控制訪問范圍,避免越界。
4.3 對非動態開辟內存使用free釋放

void test()
{int a = 10;int* p = &a;free(p); // 錯誤:不能釋放非動態分配的內存
}

解決方法:free只能用于釋放由malloc、calloc或realloc分配的內存。
4.4 使用free釋放一塊動態開辟內存的一部分

void test()
{int* p = (int*)malloc(100);p++; // p不再指向動態內存的起始位置free(p); // 錯誤:不能釋放非起始位置的內存
}

解決方法:free必須釋放動態分配的內存的起始位置。
4.5 對同一塊動態內存多次釋放

void test()
{int* p = (int*)malloc(100);free(p);free(p); // 錯誤:重復釋放
}

解決方法:釋放內存后,將指針置為NULL,避免重復釋放。
4.6 動態開辟內存忘記釋放(內存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1); // 模擬長時間運行
}

解決方法:動態分配的內存必須在不再使用時釋放,避免內存泄漏。

五、動態內存經典筆試題分析

5.1 題目1

void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);strcpy(str, "hello world");printf("%s", str);
}

問題:運行Test函數會有什么樣的結果?
答案:程序會崩潰。對NULL指針解引用操作,程序會崩潰。內存泄露。
5.2 題目2

char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf("%s", str);
}

問題:運行Test函數會有什么樣的結果?
答案:程序會輸出垃圾數據或崩潰。GetMemory函數返回的是局部數組p的地址,而局部數組在函數返回后會被銷毀,因此str指向的是無效內存。
5.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("%s", str);
}

問題:運行Test函數會有什么樣的結果?
答案:程序正常運行,輸出hello。GetMemory函數通過指針的指針p正確地修改了str的值。但是會造成內存泄露
5.4 題目4

void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf("%s", str);}
}

問題:運行Test函數會有什么樣的結果?
答案:程序可能會崩潰或輸出垃圾數據。free釋放了str指向的內存后,str變成了野指針,再次訪問會導致未定義行為。

六、柔性數組

柔性數組是C99標準中引入的一種特殊數組類型,允許結構體的最后一個成員是一個未知大小的數組。例如:

struct st_type
{int i;int a[0]; // 柔性數組成員
};

柔性數組的特點如下:
結構體中柔性數組成員前必須至少有一個其他成員。
sizeof返回的結構體大小不包括柔性數組的內存。
包含柔性數組成員的結構體必須通過malloc動態分配內存,并且分配的內存應該大于結構體的大小,以適應柔性數組的預期大小。
6.1 柔性數組的使用

#include <stdio.h>
#include <stdlib.h>typedef struct st_type
{int i;int a[0]; // 柔性數組成員
} type_a;int main()
{type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int)); // 分配內存p->i = 100;for (int i = 0; i < 100; i++){p->a[i] = i; // 使用柔性數組}free(p); // 釋放內存return 0;
}

6.2 柔性數組的優勢
柔性數組相比傳統的指針成員有以下優勢:
方便內存釋放:一次性分配內存,用戶只需調用一次free即可釋放所有內存。
提高訪問速度:連續的內存有利于提高訪問速度,減少內存碎片。

七、C/C++程序內存區域劃分

C/C++程序的內存分為以下幾個區域:
棧區(stack):用于存儲函數內的局部變量、函數參數、返回數據和返回地址等。棧內存分配效率高,但容量有限。
堆區(heap):由程序員動態分配和釋放內存,若程序員不釋放,程序結束時可能由操作系統回收。
數據段(靜態區):存放全局變量和靜態數據,程序結束后由系統釋放。
代碼段:存放函數體的二進制代碼。
在這里插入圖片描述

八、總結

動態內存管理是C語言編程中的重要組成部分,它為程序員提供了極大的靈活性,但也帶來了許多潛在的風險。通過本文的介紹,相信你已經對動態內存管理有了更深入的理解。在實際編程中,一定要注意避免常見的錯誤,合理使用malloc、calloc、realloc和free等函數,確保程序的穩定性和安全性。

希望本文對你有所幫助!如果還有其他問題,歡迎在評論區留言討論。

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

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

相關文章

expres路由模塊化

Express 路由模塊化是實際開發中非常重要的一部分&#xff0c;可以讓你的項目結構更清晰、維護更方便。 &#x1f9f1; 一、為什么要模塊化&#xff1f; 隨著項目變大&#xff0c;如果所有路由都寫在 app.js 中&#xff0c;會很亂。使用模塊化后可以&#xff1a; 功能解耦&a…

C語言——填充矩陣

C語言——填充矩陣 一、問題描述二、格式要求1.輸入形式2.輸出形式3.樣例 三、實驗代碼 一、問題描述 編程實現自動填充nn矩陣元素數值&#xff0c;填充規則為&#xff1a;從第一行最后一列矩陣元素開始按逆時針方向螺旋式填充數值1&#xff0c;2&#xff0c;…&#xff0c;nn…

零基礎上手Python數據分析 (22)案例實戰]之利用 Matplotlib Seaborn 進行電商銷售數據可視化分析

寫在前面 —— 圖表為刃,洞察先行!綜合運用 Pandas、Matplotlib 與 Seaborn,點亮數據價值 本篇通過一個完整的案例實戰,體驗如何將數據分析與數據可視化緊密結合,讓冰冷的數據轉化為生動、直觀、富有洞察力的視覺故事! 案例目標: 本篇博客將延續我們在第 17 篇案例中…

Java開發經驗總結

只要刪繁、捋清脈絡&#xff0c;才能掌握本質&#xff01;只有創新才有價值&#xff0c;保持創新、保持學習&#xff01; 計劃&#xff1a;UNIAPPSPRINGBOOT學習、SPRINGBOOTVUE新版學習、頁面展示學習、PYTHON。 ***********************************************************…

深入解析:RocketMQ、RabbitMQ和Kafka的區別與使用場景

互聯網大廠Java求職者面試&#xff1a;RocketMQ、RabbitMQ和Kafka的深入解析 故事場景&#xff1a;嚴肅且專業的面試官與架構師程序員馬架構 在一家知名的互聯網大廠&#xff0c;Java求職者正在接受一場嚴格的面試。面試官是一位經驗豐富的技術專家&#xff0c;他將通過多輪提…

使用vue2開發一個醫療預約掛號平臺-前端靜態網站項目練習

對于后端開發的我&#xff0c;最近一直在學習前端開發&#xff0c;除了要學習一些前端的基礎知識外&#xff0c;肯定少不了一些前端項目練習&#xff0c;就通過前端的編程知識 就簡單做一個醫療預約掛號前端靜態頁面。這個網站主要是使用了vue2 的相關技術實現的。 主要實現了這…

MongoDB(docker版)備份還原

docker啟動MongoDB docker run -d -p 27017:27017 --name my-mongo -v /mongodb/db:/data/db mongo備份MongoDB 使用mongodump備份數據庫時&#xff0c;默認會將備份數據保存在當前工作目錄下的dump文件夾中。 docker容器中默認備份在當前工作目錄&#xff0c;所以此處指定當…

zkPass案例實戰之合約篇

目錄 一、contracts/contracts/ProofVerifier.sol 1. License 和 Solidity 版本 2. 導入依賴 3. 合約聲明和默認分配器地址 4. 驗證證明 5. 驗證分配器簽名 6. 驗證驗證者簽名 7. 簽名前綴處理 8. 簽名恢復 總結 二、contracts/contracts/SampleAttestation.sol 1. …

ElasticSearch:高并發場景下如何保證讀寫一致性?

在Elasticsearch高并發場景下&#xff0c;可以通過以下多種方式來保證讀寫一致性&#xff1a; 等待主分片和副本分片都確認&#xff08;類似半同步機制&#xff09; 設置consistency參數&#xff1a;在寫操作時&#xff0c;可以設置consistency參數來控制寫操作的一致性級別。…

8、constexpr if、inline、類模版參數推導、lambda的this捕獲、初始化列表、namespace---c++17

一、constexpr if&#xff1a;編譯時條件分支 作用&#xff1a;在模板編程中&#xff0c;根據條件在編譯時選擇不同的代碼路徑&#xff0c;無需特化版本或復雜SFINAE技巧[替代SFINAE]。[SFINAE將在模版元編程再講。下個月了。] 注意&#xff1a;默認使用了隱式inline 基本語法…

【Java設計模式及實踐學習-第4章節-結構型模式】

第4章節-結構型模式 筆記記錄 1. 適配器模式2. 代理模式3. 裝飾器模式4. 橋接模式5. 組合模式6. 外觀模式7. 享元模式8. 總結 1. 適配器模式 2. 代理模式 3. 裝飾器模式 4. 橋接模式 5. 組合模式 6. 外觀模式 7. 享元模式 Java語言中的String字符串就使用了享元模式&…

unity基礎自學2.3:移動和抓握物品

文章目錄 前言&#xff1a;1、基礎配置①XR Interaction Toolkit②創建一個XR場景③示例文件實現④ 一鍵配置&#xff08;PICO Building Blocks&#xff09; 2、射線移動物品和抓握物品方法一&#xff1a;Grab Interactable方法二&#xff1a;prefab 3、Box Collider的作用與使…

pytest基礎-new

規范 1、首先創建 py 文件命名以 test_ 開始或者以 _test 結尾 2、若是新建類&#xff0c;測試類需要以 Test_開頭 3、測試用例&#xff08;方法&#xff09;需要以 test_開頭 assert 斷言 assert xx&#xff1a;判斷 xx 為真 assert not xx&#xff1a;判斷 xx 不為真 asse…

【華為OD機試真題】232、統計射擊比賽成績 | 機試真題+思路參考+代碼分析(C++)

題目描述 給定一個射擊比賽成績單,包含多個選手若干次射擊的成績分數,請對每個選手按其最高3個分數之和進行降序排名,輸出降序排 名后的選手ID序列 條件如下: 1.一個選手可以有多個射擊成績的分數,且次序不固定 2.如果一個選手成績少于3個,則認為選手的所有成績無效,排名…

?Unity 開發 | 如何通過 NTP 網絡時間實現精準的跨平臺時間同步【附完整源碼 + UI 模塊 + 偏差分析】

&#x1f3ae; 項目實戰 | 實現一套精確、可視化的游戲時間同步機制&#xff0c;讓你的多人在線游戲擺脫“時間不一致”噩夢&#xff01; 效果如圖&#xff1a; &#x1f4cc; 一、前言&#xff1a;為什么不能只信本地時間&#xff1f; 在 Unity 游戲開發中&#xff0c;時間幾…

Vue3 Composition API與十大組件開發案例詳解

文章目錄 一、Vue3核心API解析1.1 Composition API優勢1.2 核心API 二、十大組件開發案例案例1&#xff1a;響應式表單組件案例2&#xff1a;動態模態框&#xff08;Teleport應用&#xff09;案例3&#xff1a;可復用列表組件案例4&#xff1a;全局狀態通知組件案例5&#xff1…

Kafka 詳細解讀

1. Producer&#xff08;生產部卷王&#xff09; 職責&#xff1a;往 Kafka 里瘋狂輸出數據&#xff0c;KPI 是「日拋式消息海嘯」 職場人設&#xff1a; 白天開會畫餅&#xff0c;深夜寫周報的奮斗逼&#xff0c;口頭禪是「這個需求今晚必須上線&#xff01;」代碼里的「福報…

LicheeRV Nano 與Ubuntu官方risc-v 鏡像混合

LicheeRV Nano 官方給的鏡像并沒有unbutu, unbutu官方有一個基于 LicheeRV Dock的鏡像&#xff0c;想象能否將二者混合 &#xff08;1&#xff09;刷 LicheeRV Dock的鏡像 nano無法啟動 &#xff08;2&#xff09;將nano的boot分區替換掉 LicheeRV Dock的rootfs以外的分區也…

【模板匹配】圖像處理(OpenCV)-part10

19.1模板匹配 模板匹配就是用模板圖&#xff08;通常是一個小圖&#xff09;在目標圖像&#xff08;通常是一個比模板圖大的圖片&#xff09;中不斷的滑動比較&#xff0c;通過某種比較方法來判斷是否匹配成功,找到模板圖所在的位置。 不會有邊緣填充。 類似于卷積&#xff0c…

HTML:表格數據展示區

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>人員信息表</title><link rel"styl…