C語言——動態內存管理

動態內存管理詳解

  • 前言:
  • 一、為什么存在動態內存分配
  • 二、動態內存函數
    • 2.1malloc函數
    • 2.2calloc函數
    • 2.3realloc函數
    • 2.4free函數
  • 三、常見的動態內存錯誤
    • 3.1 對NULL指針解引用操作
    • 3.2 對動態開辟空間的越界訪問
    • 3.3 對非動態開辟內存使用free釋放
    • 3.4 使用free釋放動態開辟內存的一部分
    • 3.5 對同一塊內存多次釋放
    • 3.6 動態開辟內存忘記釋放(內存泄漏)
  • 四、幾個經典的例子
  • 五、c/c++程序的內存分配
  • 六、柔性數組
    • 6.1柔型數組的特點
    • 6.2 柔性數組的使用
    • 6.3柔性數組的優勢

前言:

我們一般開辟內存是直接開辟空間,開辟了空間就不會改變了,為了更節約空間,避免浪費空間,我們可以動態的開辟空間。這樣,空間用完了,我們可以擴充空間。

一、為什么存在動態內存分配

我們已經掌握的內存開辟方式:

int vai=10; / /在棧空間上開辟四個字節。
int arr[10]={0}; / /在棧空間上開辟40個字節的連續空間。
特點:

  1. 空間開辟大小是固定的。
  2. 數組在申明的時候,必須指定長度,才能在編譯的時候分配空間。
    對于空間的需求,不僅僅是上述的情況,有時候我們需要的空間大小只有在程序運行的時候才會知道,數組在編譯時開辟空間的方式就不能滿足了。

二、動態內存函數

關于動態內存函數的知識,可以參考我的另一篇文章動態內存函數詳解

2.1malloc函數

void* malloc(size_t size);

malloc函數向內存申請一塊連續可用的空間,返回指向這個空間的指針。
malloc函數申請到空間后直接返回這塊空間的起始地址,不會初始化空間的內容。
malloc申請的空間,當程序退出時,還給操作系統,當程序不退出,動態申請的內存,不會主動釋放,需要用free函數來釋放

2.2calloc函數

void* calloc(size_t num , size_t size) ;

calloc函數也可以申請動態內存空間。并且給空間初始化為0。

2.3realloc函數

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

realloc函數可以申請動態內存空間,使動態內存空間管理更靈活。

2.4free函數

void* free (void* ptr ) ;

free函數只能釋放動態開辟的內存。

三、常見的動態內存錯誤

3.1 對NULL指針解引用操作

可能運行成功

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(10 * sizeof(int));*p = 20;//如果p的值是NULL就會有問題free(p);
}
int main()
{test();return 0;
}

解決方法:需要對空指針進行判定(空指針不能被賦值,就是p為空不能解引用)

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}*p = 20;//如果p的值是NULL就會有問題free(p);
}
int main()
{test();return 0;
}

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

越界訪問系統會崩潰

#include <stdio.h>
#include <stdlib.h>
void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}for (i = 0; i <= 10; i++){*(p + i) = i + 1;//當i=10時,越界訪問}free(p);
}
int main()
{test();return 0;
}

解決:因為要訪問的空間存在越界問題,那么我們就讓它不越界訪問

#include <stdio.h>
#include <stdlib.h>
void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}for (i = 0; i < 10; i++)//改為i < 10{*(p + i) = i + 1;}free(p);
}
int main()
{test();return 0;
}

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

程序崩潰

#include <stdio.h>
#include <stdlib.h>
void test()
{int a = 10;int* p = &a;//釋放非動態開辟內存空間有問題free(p);//不行,運行程序系統崩潰
}
int main()
{test();return 0;
}

解決方法:非動態開辟的內存不要用free函數釋放

3.4 使用free釋放動態開辟內存的一部分

系統崩潰

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}int i = 0;for (i = 0; i < 5; i++){*p = i;p++;}//釋放free(p);//p不在指向動態內存的起始位置p = NULL;
}
int main()
{test();return 0;
}

解決問題:動態開辟內存起始位置不能亂使用,釋放的時候要從起始位置全部釋放(開辟的一塊連續的空間)

p++;(這里p不是指向開辟空間的起始位置,釋放的是后面的一部分,前面沒有釋放,程序崩潰了。)

3.5 對同一塊內存多次釋放

程序崩潰了,不允許這么操作。

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}//使用//釋放free(p);//釋放free(p);//重復釋放,程序就崩了
}
int main()
{test();return 0;
}

解決方法:第一次釋放結束,把p置NULL,下一次釋放,釋放空指針,啥事都不干。

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}//使用//釋放free(p);//釋放結束,把p置空p = NULL;//釋放free(p);
}
int main()
{test();return 0;
}

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

找不到p,沒有釋放技術內存泄漏

#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(100);if (p == NULL){perror("malloc");return;}*p = 20;
}
int main()
{test();//回來之后,p被銷毀while (1);return 0;
}
  1. 把100個字節空間的起始地址賦值給p。
  2. p是函數里的局部變量。
  3. 出了這個函數p被銷毀,找不到這100個字節的空間了,但是這100個字節的空間還在。

解決方法:只有兩種方式可以銷毀。

  • free函數釋放。
  • 程序結束(退出)

注意:
動態申請的內存空間不會因為出了作用域就自動銷毀,也·不會把內存還給操作系統。

四、幾個經典的例子

題目1:
存在兩個問題

#include <stdio.h>
#include <stdlib.h>
void Getmemory(char* p)
{p = (char*)malloc(100);//沒有釋放空間,存在內存泄漏的問題
}
void test()
{char* str = NULL;Getmemory(str);strcpy(str, "hello world");//不能對空指針進行解引用操作,程序會崩潰。printf(str);
}
int main()
{test();return 0;
}

題目2:
存在一個問題

#include <stdio.h>
#include <string.h>
char* Getmemory()
{char p[] = "hello world";//函數結束,內存釋放,空間還給操作系統。
}
void test(void)
{char* str = NULL;str=Getmemory();//記住了地址,卻找不到數據(這是個野指針,它非法訪問)strcpy(str, "hello world");printf(str);
}
int main()
{test();return 0;
}

題目3:
存在一個問題

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Getmemory(char** p, int num)
{*p = (char*)malloc(num);
}
void test(void)
{char* str = NULL;Getmemory(&str,100);strcpy(str, "hello world");printf(str);//內存泄漏//free(str);//str=NULL;
}
int main()
{test();return 0;
}

題目4:
存在一個問題

#include <stdio.h>
#include <string.h>
#include <stdlib.h>void test(void)
{char* str =(char*) malloc(100);strcpy(str, "hello");free(str);//內存被釋放,str成為野指針。if (str != NULL)//肯定成立{strcpy(str, "world");//非法訪問printf(str);}printf(str);
}
int main()
{test();return 0;
}

五、c/c++程序的內存分配

初步了解c/c++中內存區域的劃分:
在這里插入圖片描述
1.棧區(stack) :在執行函數時, 函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。棧區主要存放運行函數而分配的局部變量、 函數參數、返回數據、返回地址等。
2.堆區(heap) :一般由程序員分配釋放, 若程序員不釋放, 程序結束時可能由OS回收,分配方式類似于鏈表,
3.數據段(靜態區) (static): 存放全局變量,靜態數據。 程序結束后由系統釋放。
4.代碼段:存放函數體(類成員函數和全局函數)的二進制代碼。
注意:
實際上普通的局部變量是在棧區分配空間的,棧區的特點是在上面創建的變量出了作用域就銷毀。但是被static修飾的變量存放在數據段(靜態區),數據段的特點是在上面創建的變量,直到程序結束才銷毀。
所以static修飾的變量生命周期變長了。

六、柔性數組

結構體中,最后一個成員是數組或者是指針,他們是有區別的。
c99中,結構體的最后一個成員可以是未知大小的數組,這叫柔性數組的成員。
結構體:

typedef struct st
{int i;int arr[];  
}ts;

6.1柔型數組的特點

結構體中柔性數組前面必須至少有一個其他成員。
sizeof返回的這種結構的大小不包括柔性數組的內存。(只計算柔性數組前面成員的大小)
包含柔性數組成員的結構用malloc()函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。
如:

ts* ps = (ts*)malloc(sizeof(ts) + 40);

6.2 柔性數組的使用

int main()
{
int i = 0;
ts* p = (ts*)malloc(sizeof(ts) + 100 * sizeof(int));
if (p == NULL)
{perror("malloc");return 0 ;
}p->i = 100;
for (i = 0; i < 100; i++)
{p->arr[i] = i + 1;
}
free(p);
p = NULL;
return 0;
}

柔性數組成員a,相當于獲得了100個整型元素的連續空間

6.3柔性數組的優勢

代碼1:

int i = 0;
ts* p = (ts*)malloc(sizeof(ts) + 100 * sizeof(int));
if (p == NULL)
{perror("malloc");return 0 ;
}p->i = 100;
for (i = 0; i < 100; i++)
{p->arr[i] = i + 1;
}
free(p);
p = NULL;

代碼2:

typedef struct st
{int i;int* p;
}ts;
ts* ps = (ts*)malloc(sizeof(ts));
ps->i = 100;
ps->p = (int*)malloc(ps->i * sizeof(int));
for (i = 0; i < 100; i++)
{ps->p[i] = i + 1;
}
//釋放空間
free(ps->p);//先釋放里面
ps->p = NULL;
free(ps);//后釋放外面
ps = NULL;

上面代碼1代碼2可以完成相同的功能,但是代碼1的實現有兩種好處。
第一個好處:方便內存釋放
如果我們的代碼是在一個給別人用的函數中, 你在里面做了二次內存分配并把整個結構體返回給用戶。用戶調用free可以釋放結構體,但是用戶并不知道這個結構體內的成員也需要free,所以你不能指望用戶來發現這個事。所以,如果我們把結構體的內存以及其成員要的內存一次性分配好了,并返回給用戶一個結構體指針,用戶做一次free就可以把所有的內存也給釋放掉。
第二個好處:這樣有利于訪問速度
連續的內存有益于提高訪問速度,也有益于減少內存碎片。

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

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

相關文章

Unity UI內存泄漏優化

項目一運行&#xff0c;占用的內存越來越多&#xff0c;不會釋放&#xff0c;導致GC越來越頻繁&#xff0c;越來越慢&#xff0c;這些都是為什么呢&#xff0c;今天從UI方面談起。 首先讓我們來聊聊什么是內存泄漏呢&#xff1f; 一般來講內存泄漏就是指我們的應用向內存申請…

Rabbitmq消息不丟失

目錄 一、消息不丟失1.消息確認2.消息確認業務封裝2.1 發送確認消息測試2.2 消息發送失敗&#xff0c;設置重發機制 一、消息不丟失 消息的不丟失&#xff0c;在MQ角度考慮&#xff0c;一般有三種途徑&#xff1a; 1&#xff0c;生產者不丟數據 2&#xff0c;MQ服務器不丟數據…

設計HTML5列表和超鏈接

在網頁中&#xff0c;大部分信息都是列表結構&#xff0c;如菜單欄、圖文列表、分類導航、新聞列表、欄目列表等。HTML5定義了一套列表標簽&#xff0c;通過列表結構實現對網頁信息的合理排版。另外&#xff0c;網頁中還包含大量超鏈接&#xff0c;通過它實現網頁、位置的跳轉&…

C語言“牽手”微店商品詳情數據方法,微店商品詳情API接口申請指南

微店平臺的商品詳情通常包括以下信息&#xff1a; 商品名稱&#xff1a;展示商品的名稱&#xff0c;用于描述商品的特性和分類。 商品圖片&#xff1a;展示商品的圖片&#xff0c;可以有多張圖片以展示不同角度和細節。 商品價格&#xff1a;顯示商品的銷售價格&#xff0c;可…

nodejs服務后臺持續運行三種方法

nodejs服務后臺持續運行三種方法 一、利用 forever 推薦 forever是一個nodejs守護進程&#xff0c;完全由命令行操控。forever會監控nodejs服務&#xff0c;并在服務掛掉后進行重啟。 1、安裝 forever npm install forever -g 2、啟動服務 service forever start 3、使用…

小程序CSS button按鈕自定義高度之后不居中

問題&#xff1a; 按鈕設置高度后不居中 <view><button class"btn1" size"">Save</button> </view> page {font-size: 30rpx; }.btn1 {margin-top: 100rpx;height: 190rpx;background: linear-gradient(90deg, #FF8A06, #FF571…

Wi-Fi 安全在學校中的重要性

Wi-Fi 是教育機構的基礎設施&#xff0c;從在線家庭作業門戶到虛擬教師會議&#xff0c;應有盡有。大多數 K-12 管理員對自己的 Wi-Fi 網絡的安全性充滿信心&#xff0c;并認為他們現有的網絡安全措施已經足夠。 不幸的是&#xff0c;這種信心往往是錯誤的。Wi-Fi 安全雖然經常…

【數據結構OJ題】鏈表中倒數第k個結點

原題鏈接&#xff1a;https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId13&&tqId11167&rp2&ru/activity/oj&qru/ta/coding-interviews/question-ranking 目錄 1. 題目描述 2. 思路分析 3. 代碼實現 1. 題目描述 2. 思路分析 …

VectorStyler for Mac: 讓你的創意無限綻放的全新設計工具

VectorStyler for Mac是一款專為Mac用戶打造的矢量設計工具&#xff0c;它結合了功能強大的矢量編輯器和創意無限的樣式編輯器&#xff0c;讓你的創意無限綻放。 VectorStyler for Mac擁有直觀簡潔的用戶界面&#xff0c;讓你能夠輕松上手。它提供了豐富的矢量繪圖工具&#x…

JavaWeb博客項目--推薦算法--完整代碼及思路

基于用戶的協同過濾算法&#xff08;UserCF&#xff09; 因為我寫的是博客項目&#xff0c;博客數量可能比用戶數量還多 所以選擇基于用戶的協同過濾算法 重要思想 當要向用戶u進行推薦時&#xff0c;我們先找出與用戶u最相似的幾個用戶&#xff0c;再從這幾個用戶的喜歡的物…

數據可視化和數字孿生相互促進的關系

數據可視化和數字孿生是當今數字化時代中備受關注的兩大領域&#xff0c;它們在不同層面和領域為我們提供了深入洞察和智能決策的機會&#xff0c;隨著兩種技術的不斷融合發展&#xff0c;很多人會將他們聯系在一起&#xff0c;本文就帶大家淺談一下二者之間相愛相殺的關系。 …

Springboot集成ip2region離線IP地名映射-修訂版

title: Springboot集成ip2region離線IP地名映射 date: 2020-12-16 11:15:34 categories: springboot description: Springboot集成ip2region離線IP地名映射 1. 背景2. 集成 2.1. 步驟2.2. 樣例2.3. 響應實例DataBlock2.4. 響應實例RegionAddress 3. 打開瀏覽器4. 源碼地址&…

OpenShift 4 - 基于 MinIO 安裝 Red Hat Quay 鏡像倉庫

《OpenShift / RHEL / DevSecOps 匯總目錄》 說明&#xff1a;本文已經在 OpenShift 4.13 Quay 3.9 的環境中驗證 本文適合在單機 OpenShift 環境安裝 Red Hat Quay 鏡像倉庫。 另外《OpenShift 4 - 安裝 ODF 并部署紅帽 Quay (1 Worker)》也可以在單節點部署。 而《OpenShif…

前后端分離------后端創建筆記(11)用戶刪除

B站視頻&#xff1a;30-用戶刪除&結束語_嗶哩嗶哩_bilibili 1、現在我們要做一個刪除的功能 1.1 首先做一個刪除的功能接口&#xff0c;第一步先來到后端&#xff0c;做一個刪除的接口 2、刪除我們用Delete請求 3、方法名我給他改一下 3.1這里給他調一下刪除方法&#xf…

Java 中 List 集合排序方法

方式一&#xff1a; 調用List接口自己的sort方法排序 public static void main(String[] args) {List<Integer> numListnew ArrayList<>();numList.add(999);numList.add(123);numList.add(456);numList.add(66);numList.add(9);Collections.sort(numList); //使…

在一小時內構建您的深度學習應用程序

一、說明 我已經做了將近十年的數據分析。有時&#xff0c;我使用機器學習技術從數據中獲取見解&#xff0c;并且我習慣于使用經典 ML。 雖然我已經通過了神經網絡和深度學習的一些MOOC&#xff0c;但我從未在我的工作中使用過它們&#xff0c;這個領域對我來說似乎很有挑戰性。…

【Leetcode】91.解碼方法

一、題目 1、題目描述 一條包含字母 A-Z 的消息通過以下映射進行了 編碼 : A -> "1" B -> "2" ... Z -> "26"要 解碼 已編碼的消息,所有數字必須基于上述映射的方法,反向映射回字母(可能有多種方法)。例如,"11106" …

智能數據建模軟件DTEmpower 2023R2新版本功能介紹

DTEmpower是由天洑軟件自主研發的一款通用的智能數據建模軟件&#xff0c;致力于幫助工程師及工科專業學生&#xff0c;利用工業領域中的仿真、試驗、測量等各類數據進行挖掘分析&#xff0c;建立高質量的數據模型&#xff0c;實現快速設計評估、實時仿真預測、系統參數預警、設…

機器學習深度學習——自注意力和位置編碼(數學推導+代碼實現)

&#x1f468;?&#x1f393;作者簡介&#xff1a;一位即將上大四&#xff0c;正專攻機器學習的保研er &#x1f30c;上期文章&#xff1a;機器學習&&深度學習——注意力分數&#xff08;詳細數學推導代碼實現&#xff09; &#x1f4da;訂閱專欄&#xff1a;機器學習…