C語言實(shí)現(xiàn)動態(tài)內(nèi)存分配
棧區(qū)與堆區(qū)初探
C程序會對內(nèi)存進(jìn)行分區(qū),主要分為5個區(qū)域:
- 棧區(qū)(Stack)
- 堆區(qū)(Heap)
- 全局/靜態(tài)區(qū)
- 常量區(qū)(Constant)
- 代碼區(qū)
我們先主要了解前兩個:
棧內(nèi)存由編譯器自動分配和釋放,我們不需要操心。每調(diào)用一個函數(shù),都會在棧區(qū)為該函數(shù)分配一塊內(nèi)存區(qū)域,這塊區(qū)域就叫做函數(shù)棧幀。其中主要存放一些非靜態(tài)的局部變量、函數(shù)參數(shù)等。
例如,下面代碼中的函數(shù)形參 b、定義的局部變量 a 所用到的內(nèi)存,都會由編譯器自動開辟,開辟的方式是靜態(tài)內(nèi)存開辟。當(dāng) add 函數(shù)執(zhí)行完畢返回時,對應(yīng)的函數(shù)棧幀就會被銷毀,自然這些占用的內(nèi)存會被編譯器自動回收。
int add(int b) {
int a = 10;
return a + b;
}
而堆內(nèi)存由我們程序員手動分配(malloc 或 calloc)和釋放(free)。
malloc 和 calloc 的區(qū)別在于:malloc 分配的內(nèi)存存儲的都是未初始化的隨機(jī)垃圾值,而 calloc 會自動將分配的內(nèi)存全部初始化為 0。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 申請了32MB的內(nèi)存
int* arr = (int*) malloc(8 * 1024 * 1024 * sizeof(int)); // 返回值類型是void*,表示無類型指針,我們可以強(qiáng)轉(zhuǎn)賦予它類型
// 每次動態(tài)分配內(nèi)存后,都要檢查返回值是否為 NULL
if (arr == NULL) {
// 防止操作到空指針,導(dǎo)致程序崩潰
printf("Memory allocation failed!\n");
return -1;
}
// 釋放內(nèi)存
free(arr);
// 置空,防止野指針
arr = NULL;
return 0;
}
堆內(nèi)存的開辟方式是動態(tài)內(nèi)存開辟,這些內(nèi)存不會自動回收,如果不手動回收,就會造成內(nèi)存泄漏。
此外,棧空間通常很?。?MB),堆空間則很大,和系統(tǒng)可用的內(nèi)存有關(guān)。
運(yùn)行時決定內(nèi)存大小
動態(tài)內(nèi)存開辟的使用場景有很多:數(shù)據(jù)長度只在運(yùn)行時才確定、??臻g不滿足需求、需要延長變量的生命周期、內(nèi)存大小需要動態(tài)改變等。
我們以第一種場景為例:運(yùn)行時由用戶輸入決定人員的數(shù)量。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 用戶輸入
int num = 0;
printf("Please enter the number of people.\n");
scanf_s("%d", &num);
// 開辟對應(yīng)大小的空間
int* arr = (int*)malloc(num * sizeof(int));
// 檢查內(nèi)存是否開辟成功
if (arr == NULL) {
printf("Memory allocation failed!\n");
return -1; // 退出程序
}
for (int i = 0; i < num; i++)
{
// 輸入年齡
int age = 0;
printf("Please enter the age of the %d member at this position.\n", i + 1);
scanf_s("%d", &age);
arr[i] = age;
}
// 輸出每個人的年齡
for (int i = 0; i < num; i++)
{
printf("The age of the %d member is %d\n", i + 1, arr[i]);
}
// 釋放并置空
free(arr);
arr = NULL;
return 0;
}
scanf_s 是 Visual Studio 環(huán)境下特有的安全函數(shù),在非 VS 環(huán)境中請使用 scanf。
運(yùn)行結(jié)果:

realloc 的擴(kuò)容機(jī)制與暗坑
再來看看第四種場景,普通的數(shù)組一旦定義后,長度就固定了,而動態(tài)內(nèi)存的大小可以使用 realloc 進(jìn)行重新調(diào)整,根據(jù)自己的需要擴(kuò)容或縮容。
使用 realloc 進(jìn)行擴(kuò)容時,有兩種情況:
原地擴(kuò)容:如果原位置后有足夠的連續(xù)內(nèi)存空間,它會直接在原地址后追加空間,返回的地址和原地址相同。

異地擴(kuò)容:如果原位置所需的連續(xù)地址空間不足,它會嘗試在堆區(qū)找到一塊合適的內(nèi)存空間,將之前的數(shù)據(jù)拷貝到新位置,并自動釋放之前的舊內(nèi)存,最后返回指向這塊新內(nèi)存空間的指針。

因為異地擴(kuò)容很常見,所以我們應(yīng)該總是要使用新的指針去接收返回值。同時,如果發(fā)生后了異地擴(kuò)容,原來的指針就變?yōu)榱艘爸羔?,?yīng)該置為空。
#include <stdio.h>
#include <stdlib.h>
int main() {
// 初始可以存儲8個整型
int* p = (int*)malloc(8 * sizeof(int));
if (p == NULL) {
printf("Initial memory allocation failed.\n");
return -1;
}
for (int i = 0; i < 8; i++)
{
p[i] = i + 1;
}
printf("Before capacity expansion\n");
for (int i = 0; i < 8; i++)
{
printf("%d ", p[i]);
}
// 擴(kuò)容至16
printf("\nAfter capacity expansion\n");
// 使用新指針變量接收,防止因擴(kuò)容失敗導(dǎo)致原內(nèi)存地址 p 丟失
int* new_p = (int*)realloc(p, 16 * sizeof(int));
if (new_p == NULL) {
printf("\nFailed to allocate memory for expansion.\n");
// 擴(kuò)容失敗,舊內(nèi)存 p 依然有效,程序結(jié)束前記得釋放
free(p);
p = NULL;
return -1;
}
// 擴(kuò)容成功,原指針 p 可能已在異地擴(kuò)容中被自動釋放而失效,為防止誤用,我們將其置空
p = NULL;
for (int i = 8; i < 16; i++)
{
new_p[i] = i + 1;
}
for (int i = 0; i < 16; i++)
{
printf("%d ", new_p[i]);
}
// 此時由 new_p 管理這塊空間,我們只需釋放 new_p
free(new_p);
new_p = NULL;
return 0;
}
如果 realloc 擴(kuò)容失敗,它將返回 NULL 空指針,但舊內(nèi)存不會被釋放,我們需要手動處理。
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(8 * sizeof(int));
if (p == NULL) return -1;
// 嘗試申請一塊非常大的內(nèi)存,模擬失敗的情況
int* new_p = (int*)realloc(p, 8 * 1024LL * 1024 * 1024 * sizeof(int));
if (new_p == NULL)
{
printf("Failed to allocate memory.\n");
// 雖然申請新內(nèi)存失敗,但是舊內(nèi)存塊 p 依然存在,需要由我們手動釋放
free(p);
p = NULL;
}
else
{
printf("Success to allocate memory.\n");
// 如果成功,釋放新指針 new_p 即可
free(new_p);
new_p = NULL;
p = NULL; // 置空防誤用
}
return 0;
}
注意:永遠(yuǎn)不要多次釋放同一塊內(nèi)存,可能會導(dǎo)致程序崩潰。
到此這篇關(guān)于C語言實(shí)現(xiàn)動態(tài)內(nèi)存分配的文章就介紹到這了,更多相關(guān)C語言 動態(tài)內(nèi)存分配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++?system()函數(shù)的常用用法(全網(wǎng)最新)
system()用于從C?/C++程序調(diào)用操作系統(tǒng)命令,這里給大家講解下C++?system()函數(shù)的常用用法,感興趣的朋友跟隨小編一起看看吧2023-01-01
C語言實(shí)現(xiàn)學(xué)生選修課程系統(tǒng)設(shè)計
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)學(xué)生選修課程系統(tǒng)設(shè)計,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02
利用Matlab實(shí)現(xiàn)時域分析功能的示例詳解
利用MATLAB可以方便地進(jìn)行控制系統(tǒng)的時域分析。這篇文章主要通過簡單的示例為大家介紹了Matlab進(jìn)行時域分析的具體操作,需要的可以參考一下2023-02-02
C語言關(guān)鍵字auto與register的深入理解
本篇文章是對c語言關(guān)鍵字auto與register的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
函數(shù)指針的強(qiáng)制類型轉(zhuǎn)換實(shí)現(xiàn)代碼
函數(shù)指針的強(qiáng)制類型轉(zhuǎn)換實(shí)現(xiàn)代碼。需要的朋友可以過來參考下,希望對大家有所幫助2013-10-10

