C語言學習之動態內存的管理

? ? ? ? 學完前面的C語言內容后,我們之前給內存開辟空間的方式是這樣的。

int val=20;
char arr[10]={0};

? ? ? ? ?我們發現這個方式有兩個弊端:空間是固定的;同時在聲明的時候必須指定數組的長度,一旦確定了大小就不能調整的。

? ? ? ? 而實際應用的過程中,我們發現定長的數組往往是不能滿足需要的。因此我們需要對內存進行動態化的處理。

目錄

malloc函數

free函數

calloc函數

realloc函數

動態內存管理的幾個常見錯誤

對空指針解引用

對動態開辟內存的越界訪問

對非動態內存使用free函數

?使用free函數釋放了一部分

同一動態內存多次釋放

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

一些經典的內存方面的例題:

1.

2.

3.

4.

柔性數組

柔性數組的特點

? ? ? ? 柔性數組的使用

? ? ? ? 柔性數組的優勢

C/C++中內存區域劃分


內存三大區域主要存儲的數據類型。

malloc函數

? ? ? ? malloc是C語言動態內存開辟的一個函數,它的語法形式是這樣的

void * malloc(size_t size)

?????????其中size是指定的大小(字節)

? ? ? ? 這個函數就是向內存申請一塊連續可用的空間,并返回這塊空間的指針

? ? ? ? 如果開辟成功則返回一個指向開辟好空間的指針則返回一個指向開辟好空間的指針。;如果開辟失敗則返回一個NULL指針,因此一定要對malloc返回值做檢查。

? ? ? ? 返回值類型為void*,所以malloc函數并不知道開辟空間的類型,具體使用的時候使用者自己決定。

? ? ? ? 如果參數size的數值為0,則malloc的行為標準是未定義,具體行為取決于編譯器。

? ? ? ? 使用該函數前需要包含頭文件<stdlib.h>

? ? ? ? 但是當我們申請空間調用后一定要銷毀內存空間,因此我們還需要free函數

free函數

? ? ? ? free函數專門用來做動態內存的釋放和回收的函數。語法結構如下:

void *free(void *ptr)

????????ptr中存放的是要釋放的空間的起始位置。

? ? ? ? 如果ptr指向的內存空間不是動態的,free行為未定義;如果ptr指向的內存是NULL指針,則函數什么都不做。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{int *p=(int*)malloc(sizeof(int)*10);if (p == NULL){perror("空間申請失敗");return 1;//異常返回,退出程序}//使用內存for (int i = 0; i < 10; i++){p[i] = i;}for (int i = 0; i < 10; i++){printf("%d ", p[i]);}//釋放內存free(p);//如果這里不寫free函數則程序運行時候系統自動回收這些內存。//但是可能導致內存泄漏//同時這么寫是很危險的,因為p被釋放時候就是野指針了。后續如果接著調用p,可能會導致程序崩潰//所以要在使用完內存之后立即將p置為NULLp = NULL;return 0;
}

calloc函數

? ? ? ? calloc函數也可以用來動態內存分配,語法結構如下:

void*calloc(size_t num,size_t size)

? ? ? ? 功能是給num個size元素開辟一塊空間,并將其初始化為零。

? ? ? ? 看著與malloc的功能相似,區別是calloc在返回地址之前吧申請的空間每個字節全部初始化為0。

? ? ? ? malloc效率更高一點,calloc不需要初始化。

realloc函數

? ? ? ? realloc函數讓動態內存更加靈活的調整。

? ? ? ? 如果發現申請空間過小或者過大的時候,為了合理使用內存,靈活的調整內存的大小,而realloc函數就是為了這個而生的。

? ? ? ? 它的語法結構如下:

void*realloc(void *ptr,size_t size)

? ? ? ? ptr是要調整的內存的起始位置,size是調整后新的內存大小(單位為字節)

? ? ? ? 返回值為調整后內存起始位置。

? ? ? ? 這個函數調整原有內存的大小基礎上會將原數據遷移到新空間。

?使用realloc幾種情況

1.后面有足夠大的空間,直接擴容。

2.后面空間足夠但是被占用了,所以在新空間找一塊足夠大滿足條件的內存空間,將舊空間數據拷貝到新的空間,隨后釋放掉舊空間并返回新空間的地址

動態內存管理的幾個常見錯誤

對空指針解引用

????????

#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i < 10; i++){*(p + i) = i=1; //可能產生空指針解引用操作}return 0;
}

所以要判斷malloc返回值

對動態開辟內存的越界訪問

? ? ? ? 之前我們知道數組是不能越界訪問的。動態內存也是如此,申請的時候也是有大小的,必須要在自己的范圍內使用,超出范圍就是非法訪問。
? ? ? ? 錯誤寫法

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = malloc(sizeof(int) * 10);int i = 0;for (i = 0; i <= 10; i++){*(p + i) = i=1; //當i為10的時候形成越界訪問了}return 0;
}

對非動態內存使用free函數

錯誤寫法

#include<stdio.h>
#include<stdlib.h>
int main()
{int a = 10;int* p = &a;//使用*p = 100;free(p);p = NULL;return 0;
}

?使用free函數釋放了一部分

錯誤寫法:

#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return -1;}int i = 0;for (i = 0; i < 5; i++){*p = 5;p++;}free(p);//p指向的不再是動態開辟的空間的起始地址。p = NULL;return 0;
}

同一動態內存多次釋放

????????? ? ? ?錯誤寫法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return 1;}free(p);free(p);//釋放兩次,第二次釋放會導致程序崩潰。
}
int main()
{test();return 0;
}

? ? ? 可以這樣改正

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p == NULL){printf("內存分配失敗!\n");return 1;}free(p);p = NULL;free(p);//釋放兩次,第二次釋放會導致程序崩潰。
}
int main()
{test();return 0;
}

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

? ? ? ? 錯誤寫法:

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}
}
int main()
{test();while (1);//無法知道前面申請10個字節的地址return 0;
}

正確寫法:要在函數之內釋放內存

? ? ? ? 或者也可以這樣

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}return p;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//釋放pr = NULL;while (1);//無法知道前面申請10個字節的地址return 0;
}

? ? ? ? 只要保證一個原則:malloc、calloc、realloc必須要和free函數成對出現。

? ? ? ? realloc函數也能實現malloc函數的效果

????????但是即使你成對存在,也可能內存泄漏

????????如下圖所示,在test函數中,在釋放內存之前就已經返回了,所以內存沒有釋放,因此內存泄漏。

#include<stdio.h>
#include<stdlib.h>
int test()
{int* p=malloc(sizeof(int)*10);if (p != NULL){*p = 10;}int n = 20;if (n > 10){//代碼}return p;free(p);p = NULL;
}
int main()
{int *pr=test();//使用*pr = 100;free(pr);//釋放pr = NULL;while (1);//無法知道前面申請10個字節的地址return 0;
}

一些經典的內存方面的例題:

1.

void GetMemory(char *p)
{p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(str);strcpy(str, "Hello World!");printf("%s\n", str);
}

運行test()函數后的結果:

運行崩潰。

解析:這里面,test函數中GetMemory函數的調用是直接將指針變量str本身傳遞過去了,是傳值調用,str的值沒有變化,仍然是NULL,所以在下一步進入strcpy函數,在strcpy函數中會對NULL進行解引用,造成了非法訪問,程序就會崩潰。

可以這么更改:(這種方法更好一點)

void GetMemory(char **p)
{*p = (char*)malloc(100);
}
void test()
{char* str = NULL;GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

也可以這樣改 :

char* GetMemory(char **p)
{*p = (char*)malloc(100);return p;
}
void test()
{char* str = NULL;str=GetMemory(&str);strcpy(str, "Hello World!");printf("%s\n", str);//printf(str)也可以free(str);str=NULL;
}

2.

char *GetMemory()
{char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

?運行test函數的后果:

運行結果錯誤。

解析:p的地址可以正常傳遞給str,但是p數組是函數的局部變量,出了函數就會被回收,p數組的內收可能就被改了。這個就是返回棧空間地址的問題。

棧區上空間要么free函數回收,要么程序結束回收。

可以這樣改:

char *GetMemory()
{static char p[] = "Hello World!";return p;
}
void test()
{char* str = NULL;str=GetMemory(str);printf(str);
}

3.

void GetMemory(char **p,int num)
{*p=(char*)malloc(num);
}
void test()
{char* str = NULL;GetMemory(&str,100);strcpy(str,"hello");printf(str);
}

求test函數的運行結果

?程序崩潰

解析:內存沒有釋放。

4.

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

求運行test函數的結果:

運行錯誤。

解析:str在free函數之后沒有置為NULL。

這些題目出自于《高質量C/C++編程》

柔性數組

? ? ? ? 柔性數組在結構體中,且最后一個成員是未知大小的數組,這個數組就是柔性數組。

struct S
{int a;int S[];//未指明大小,就是柔性數組
};

? ? ? ? 有些編譯器可能不支持這種寫法,可以改成

struct S
{int a;int S[0];//未指明大小,就是柔性數組
};

柔性數組的特點

? ? ? ? 結構體中柔性數組前至少要有一個成員

? ? ? ? sizeof返回這種結構大小不包括柔性數組

? ? ? ? 包含柔性數組的結構用malloc進行動態內存分配,并且分配的內存應該大于該結構的大小以適應柔性數組預期大小。

#include<stdio.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性數組
}st;
int main()
{printf("%zd\n", sizeof(st));return 0;
}

結果為5。

? ? ? ? 柔性數組的使用

????????

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int a;char c;int S[0];//未指明大小,就是柔性數組
}st;
int main()
{st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間if (p == NULL){perror("malloc error");return -1;}//使用內存p->a = 10;p->c = 0;for (int i = 0; i < 5; i++){p->S[i] = i+1;}//空間不夠// 擴容st*q=(st*)realloc(p, sizeof(st) + 40 * sizeof(int));if (q != NULL){p = q;q = NULL;}//釋放內存free(q);q = NULL;return 0;
}

應用二:相當于獲得了10個整型元素空間

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int S[];//未指明大小,就是柔性數組
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間p->i = 100;for (i = 0; i < 5; i++){p->S[i] = i+1;}free(p);p = NULL;return 0;
}

? ? ? ? 柔性數組的優勢

? ? ? ? 上圖代碼也可以這樣寫:

#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{int i;int *p_a;
}st;
int main()
{int i = 0;st*p=(st*)malloc(sizeof(st)+10*sizeof(int));//分配10個int的空間p->i = 100;p->p_a = (int*)malloc(p->i*sizeof(int));for (i = 0; i < 5; i++){p->p_a[i] = i+1;}free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

? ? ? ? 二者 均可,但是方法一有兩大好處:

1.方便內存釋放

2.有利于訪問速度

C/C++中內存區域劃分

C/C++中內存劃分的幾個區域

1.棧區(stack):在執行函數時,函數內部局部變量的儲存單元都可以在棧上創建。函數執行結束時這些儲存單元自動被釋放。棧內存分配內置于處理器指令集中,效率很高,但是分配的內存容量有限。棧區主要是存放運行函數而分配的局部變量、函數參數、返回數據、返回地址等(詳細了解可以參考《函數棧幀的創建與銷毀》)

2.堆區(heap):一般由程序員分配釋放,若程序員不釋放,程序結束可能由操作系統釋放。分配方式類似于鏈表

3.數據段(靜態區):(static)存放全局變量、靜態數據。程序結束后由系統釋放

4.代碼段:存放函數體(類成員函數和全局函數)的二進制代碼段

具體可以參考如下:

????????

感謝看到這里的讀者大大們,求一個贊,謝謝

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

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

相關文章

【深度學習-Day 2】圖解線性代數:從標量到張量,理解深度學習的數據表示與運算

Langchain系列文章目錄 01-玩轉LangChain&#xff1a;從模型調用到Prompt模板與輸出解析的完整指南 02-玩轉 LangChain Memory 模塊&#xff1a;四種記憶類型詳解及應用場景全覆蓋 03-全面掌握 LangChain&#xff1a;從核心鏈條構建到動態任務分配的實戰指南 04-玩轉 LangChai…

首頁數據展示

排版 現在做首頁的排版&#xff0c;依舊是偷antd里面的東西 使用card包裹list的樣式 import React from react import axios import { Card, Col, Row, List } from antd import { EditOutlined, EllipsisOutlined, SettingOutlined } from ant-design/icons; import { Avat…

使用Set和Map解題思路

前言 Set和Map這兩種數據結構,在解決一些題上&#xff0c;效率很高。跟大家簡單分享一些題以及如何使用Set和Map去解決這些題目。 題目鏈接 136. 只出現一次的數字 - 力扣&#xff08;LeetCode&#xff09; 138. 隨機鏈表的復制 - 力扣&#xff08;LeetCode&#xff09; 舊…

嘗試leaflet+webassemly

前言 筆者在github發現rust版本的leaflet&#xff0c;發現是用wasm-bindgen包裝的&#xff0c;嘗試使用一下 Issues slowtec/leaflet-rshttps://github.com/slowtec/leaflet-rs 正文 準備 新建一個react項目&#xff0c;安裝rsw依賴 pnpm i -D vite-plugin-rsw cargo ins…

機器學習實戰,天貓雙十一銷量與中國人壽保費預測,使用多項式回歸,梯度下降,EDA數據探索,彈性網絡等技術

前言 很多同學學機器學習時總感覺&#xff1a;“公式推導我會&#xff0c;代碼也能看懂&#xff0c;但自己從頭做項目就懵”。 這次我們選了兩個小數據集&#xff0c;降低復雜度&#xff0c;帶大家從頭開始進行分析&#xff0c;建模&#xff0c;預測&#xff0c;可視化等&…

SQL數據庫系統全解析:從入門到實踐

一、數據庫世界入門指南 在數字時代&#xff0c;數據就像新時代的石油&#xff0c;而數據庫系統就是儲存和管理這些寶貴資源的倉庫。對于初學者來說&#xff0c;理解數據庫的基本概念是邁入這個領域的第一步。 數據庫本質上是一個有組織的數據集合&#xff0c;它允許我們高效…

【大模型】圖像生成:StyleGAN3:生成對抗網絡的革命性進化

深度解析StyleGAN3&#xff1a;生成對抗網絡的革命性進化 技術演進與架構創新代際技術對比StyleGAN3架構解析 環境配置與快速入門硬件要求安裝步驟預訓練模型下載 實戰全流程解析1. 圖像生成示例2. 自定義數據集訓練3. 潛在空間操作 核心技術深度解析1. 連續信號建模2. 傅里葉特…

PHP-Cookie

Cookie 是什么&#xff1f; cookie 常用于識別用戶。cookie 是一種服務器留在用戶計算機上的小文件。每當同一臺計算機通過瀏覽器請求頁面時&#xff0c;這臺計算機將會發送 cookie。通過 PHP&#xff0c;您能夠創建并取回 cookie 的值。 設置Cookie 在PHP中&#xff0c;你可…

“Everything“工具 是 Windows 上文件名搜索引擎神奇

01 Everything 和其他搜索引擎有何不同 輕量安裝文件。 干凈簡潔的用戶界面。 快速文件索引。 快速搜索。 快速啟動。 最小資源使用。 輕量數據庫。 實時更新。 官網&#xff1a;https://www.voidtools.com/zh-cn/downloads/ 通過網盤分享的文件&#xff1a;Every…

CSS:選擇器-基本選擇器

文章目錄 1、通配選擇器2、元素選擇器3、類選擇器4、ID選擇器 1、通配選擇器 2、元素選擇器 3、類選擇器 4、ID選擇器

一種動態分配內存錯誤的解決辦法

1、項目背景 一款2年前開發的無線網絡通信軟件在最近的使用過程中出現網絡中傳感器離線的問題&#xff0c;此軟件之前已經使用的幾年了&#xff0c;基本功能還算穩定。這次為什么出了問題。 先派工程師去現場調試一下&#xff0c;初步的結果是網絡信號弱&#xff0c;并且有個別…

React 第三十四節 Router 開發中 useLocation Hook 的用法以及案例詳解

一、useLocation基礎用法 作用&#xff1a;獲取當前路由的 location 對象 返回對象結構&#xff1a; {pathname: "/about", // 當前路徑search: "?namejohn", // 查詢參數&#xff08;URL參數&#xff09;hash: "#contact", …

DeepSeek-Prover-V2-671B最新體驗地址:Prover版僅適合解決專業數學證明問題

DeepSeek-Prover-V2-671B最新體驗地址&#xff1a;Prover版僅適合解決專業數學證明問題 DeepSeek 團隊于 2025 年 4 月 30 日正式在Hugging Face開源了其重量級新作 —— DeepSeek-Prover-V2-671B&#xff0c;這是一款專為解決數學定理證明和形式化推理任務而設計的超大規模語…

tornado_登錄頁面(案例)

目錄 1.基礎知識?編輯 2.腳手架&#xff08;模版&#xff09; 3.登錄流程圖&#xff08;processon&#xff09; 4.登錄表單 4.1后&#xff08;返回值&#xff09;任何值&#xff1a;username/password &#xff08;4.1.1&#xff09;app.py &#xff08;4.1.2&#xff…

Android學習總結之自定義view設計模式理解

面試題 1&#xff1a;請舉例說明自定義 View 中模板方法模式的應用 考點分析 此問題主要考查對模板方法模式的理解&#xff0c;以及該模式在 Android 自定義 View 生命周期方法里的實際運用。 回答內容 模板方法模式定義了一個操作的算法骨架&#xff0c;把一些步驟的實現延…

【Scrapy】簡單項目實戰--爬取dangdang圖書信息

目錄 一、基本步驟 1、新建項目 &#xff1a;新建一個新的爬蟲項目 2、明確目標 &#xff08;items.py&#xff09;&#xff1a;明確你想要抓取的目標 3、制作爬蟲 &#xff08;spiders/xxspider.py&#xff09;&#xff1a;制作爬蟲開始爬取網頁 4、存儲內容 &#xff08;p…

開源CMS系統的SEO優化功能主要依賴哪些插件?

在當今互聯網時代&#xff0c;搜索引擎優化&#xff08;SEO&#xff09;是網站獲取流量的核心手段之一。開源內容管理系統&#xff08;CMS&#xff09;因其靈活性和豐富的插件生態&#xff0c;成為許多開發者和企業的首選。本文將以主流開源CMS為例&#xff0c;深入解析其SEO優…

在 JMeter 中使用 BeanShell 獲取 HTTP 請求體中的 JSON 數據

在 JMeter 中&#xff0c;您可以使用 BeanShell 處理器來獲取 HTTP 請求體中的 JSON 數據。以下是幾種方法&#xff1a; 方法一&#xff1a;使用前置處理器獲取請求體 如果您需要在發送請求前訪問請求體&#xff1a; 添加一個 BeanShell PreProcessor 到您的 HTTP 請求采樣器…

在 WSL (Windows Subsystem for Linux) 中配置和安裝 Linux 環境

在 WSL (Windows Subsystem for Linux) 中配置和安裝 Linux 環境 WSL 允許你在 Windows 上運行 Linux 環境&#xff0c;以下是詳細的配置和安裝指南。 1. 安裝前的準備工作 系統要求 Windows 10 版本 2004 及更高版本(內部版本 19041 及更高版本)或 Windows 11 64 位系統 虛…

AlphaFold蛋白質結構數據庫介紹

AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作開發的公開蛋白質結構預測數據庫,是利用 AlphaFold2/AlphaFold3 AI模型 預測的全基因組級蛋白質三維結構庫。 網址: https://alphafold.ebi.ac.uk 項目內容主辦單位DeepMind + EMBL-EBI上線…