<C语言> 动态内存管理

1.动态内存函数

为什么存在动态内存分配?

int main(){int num = 10;  //向栈空间申请4个字节int arr[10];   //向栈空间申请了40个字节return 0;
}

上述的开辟空间的方式有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就需要动态内存开辟了。

1.1 malloc和free

malloc()是用于在程序执行期间动态分配内存。它的全称是"memory allocation",意为内存分配。malloc()函数是C标准库的一部分,它的声明在stdlib.h头文件中。

函数原型如下:

void* malloc(size_t size);

在这里,size是你想要分配的字节数,函数返回一个指向分配的内存块起始地址的指针。malloc()函数的返回类型是void*,这意味着返回的指针可以赋值给任何指针类型而无需显式转换。

下面简要解释一下malloc()的工作原理:

1.你提供想要分配的字节数,malloc()在堆内存中搜索一个足够大的连续内存块来存储这些字节。

2.如果找到了合适的内存块,它将其标记为已使用,并返回该内存块的起始地址的指针。

3.如果找不到足够大的内存块,它将返回一个NULL指针,表示内存分配失败。

注意:使用malloc()分配的内存需要使用free()函数显式地释放,否则会导致内存泄漏。

void free(void* ptr);

free()函数接受之前分配的内存块的指针,并将其释放,使其可供将来的动态分配使用。如果忘记释放之前分配的内存,程序每次运行分配代码时都会消耗更多内存,最终可能导致内存耗尽。

#include <stdio.h>
#include <stdlib.h>int main() {int n = 5;int* dynamicArray = (int*)malloc(n * sizeof(int));if (dynamicArray == NULL) {printf("内存分配失败!\n");} else {// 使用分配的内存块for (int i = 0; i < n; i++) {dynamicArray[i] = i + 1;}// 当不再需要分配的内存时,记得释放它free(dynamicArray);dynamicArray = NULL}return 0;
}

在调用free()函数释放动态分配的内存后,将指针dynamicArray设置为NULL是一个良好的习惯,但不是必须的。

设置指针为NULL的优点:

  1. 避免悬挂指针(Dangling Pointer):如果在释放内存后不将指针设置为NULL,该指针将仍然保留先前的地址。如果你在后续代码中继续使用该指针,可能会导致悬挂指针,即指针指向的内存已经被释放,这可能导致程序崩溃或产生难以调试的错误。将指针设置为NULL可以帮助你避免这种情况,因为如果尝试使用空指针,程序将产生明确的错误(空指针解引用)。
  2. 避免重复释放:在释放内存后,如果将指针设置为NULL,你可以通过检查指针是否为NULL来确定是否已经释放了内存。如果你在后续代码中错误地再次调用free(),会导致未定义的行为。

如果你在后续代码中小心地避免悬挂指针和重复释放内存,那么不设置为NULL也不会导致问题。然而,这是一个简单且有助于防范错误的额外保护措施,所以建议在释放内存后将指针设置为NULL

1.2 calloc

calloc()是另一个动态内存分配函数,也属于标准C库(stdlib.h头文件)。与malloc()功能类似,但在使用上有一些区别。

calloc()函数的原型如下:

void* calloc (size_t num, size_t size);

其中num是你想要分配的元素数量,size是每个元素的大小(以字节为单位)。calloc()函数会为num * size字节的内存块分配空间,并将该内存块中的所有位初始化为零。

相对于malloc()calloc()的一个优势是它会自动初始化分配的内存,这意味着你不需要手动将分配的内存清零。在某些情况下,这可能是非常有用的,特别是当你需要确保分配的内存一开始就是零值时。

实例:

#include <stdio.h>
#include <stdlib.h>int main() {int n = 5;int* dynamicArray = (int*)calloc(n, sizeof(int));if (dynamicArray == NULL) {printf("内存分配失败!\n");} else {// 使用分配的内存块,这里的内存已经被初始化为零for (int i = 0; i < n; i++) {printf("%d ", dynamicArray[i]); // 输出: 0 0 0 0 0}// 当不再需要分配的内存时,记得释放它free(dynamicArray);}return 0;
}

总结:

calloc = malloc+memset 初始化为0

1.3 realloc

realloc是一个用于重新分配内存块大小的函数。具体而言,它可以用于更改之前通过malloccalloc分配的内存块的大小。

realloc函数的声明如下:

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

参数说明:

  • ptr:指向之前已分配内存块的指针。如果ptr为NULL,则realloc的行为就相当于malloc,即分配一个新的内存块。
  • size:新的内存块大小,以字节为单位。

realloc的工作原理如下:

  1. 如果ptr为NULL,那么realloc的行为就等同于malloc(size),它将分配一个新的大小为size字节的内存块,并返回指向该内存块的指针。
  2. 如果size为0,且ptr不为NULL,那么realloc的行为就等同于free(ptr),即释放掉之前分配的内存块,并返回NULL指针。
  3. 如果ptr不为NULL且size不为0,realloc将尝试重新分配之前分配的内存块。可能发生以下几种情况:
    • 如果之前分配的内存块大小大于或等于size,则不会分配新的内存块,而是简单地返回原始内存块的指针,不会改变原内存块的内容。
    • 如果之前分配的内存块大小小于sizerealloc会尝试将原始内存块扩展到新的大小。这可能会**在原始内存块后面的可用内存空间进行扩展,如果没有足够的连续空间来扩展,则realloc可能会在另一个地方重新分配一个新的内存块,并将原始内容复制到新的内存块中。**这意味着realloc有可能返回一个新的指针,而不是原始指针,所以在使用realloc后,最好将返回的指针赋值给原来的指针。
    • 如果realloc在新的内存块分配失败时,将返回NULL,并且之前分配的内存块仍然保持未更改。

使用realloc时,应该特别注意以下几点:

  • 如果realloc返回NULL,表示重新分配失败,原来的指针仍然有效,为避免内存泄漏,应该保存原来的指针,并根据需要释放之前的内存块。
  • 当使用realloc时,最好不要直接修改原始指针,而是将realloc的结果赋值给原始指针,以防止意外的内存问题。

实例:

#include <stdio.h>
#include <stdlib.h>
int main() {int *p = (int *) malloc(40);if (p == NULL)return 1;//使用int i = 0;for (i = 0; i < 10; i++) {*(p + i) = i;}for (i = 0; i < 10; i++) {printf("%d ", *(p + i));}//增加空间// p = (int *)realloc(p, 80); //如果开辟失败的话,p变成了空指针,不能这么写int *ptr = (int *) realloc(p, 80);if (ptr != NULL) {p = ptr;ptr = NULL;}//当realloc开辟失败的时候,返回的也是空指针//使用for (i = 10; i < 20; i++) {*(p + i) = i;}for (i = 10; i < 20; i++) {printf("%d ", *(p + i));}//释放free(p);p = NULL;return 0;
}//输出结果:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

2.常见的动态内存错误

2.1 对NULL指针的解引用操作

#include <stdio.h>
#include <stdlib.h>
int main() {int* p = (int*)malloc(20);*p = 5;  //错误,空指针解引用//为了不对空指针解引用  需要进行判断if (p == NULL) {perror("malloc");return 1;}else {*p = 5;}free(p);p = NULL;return 0;
}

2.2 对动态开辟空间的越界访问

#include <stdio.h>
#include <stdlib.h>
int main() {int *p = (int *) malloc(20);if (p == NULL)return 1;int i = 0;for (i = 0; i < 20; i++)//越界访问    20个字节 只能访问5个整型{*(p + i) = i;}free(p);p = NULL;return 0;
}

2.3 对非动态开辟内存使用free释放

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

在这里插入图片描述

编译器会直接报错

2.4 使用free释放一块动态开辟内存的一部分

#include <stdio.h>
#include <stdlib.h>
int main() {int *p = (int *) malloc(40);if (p = NULL)return 1;int i = 0;for (i = 0; i < 5; i++) {*p = i;p++;}//释放//在释放的时候,p指向的不再是动态内存空间的起始位置free(p);// p不再指向动态内存的起始位置p++;return 0;
}

2.5 对同一块动态内存多次释放

#include <stdio.h>
#include <stdlib.h>
int main() {int* p = (int*)malloc(40);if (p == NULL)return 1;int i = 0;for (i = 0; i < 5; i++) {*(p + i) = i;}//重复freefree(p);p = NULL;//如果将p赋值为NULL  就可以在free,否则编译器会直接报错free(p);return 0;
}

2.6 动态开辟内存忘记释放(内存泄漏)

#include <stdio.h>
#include <stdlib.h>
int *get_memory() {int *p = (int *) malloc(40);return p;
}int main() {int *ptr = get_memory();//使用//释放  如果不释放 就会导致内存泄漏free(ptr);return 0;
}

3.C/C++程序的内存开辟

在这里插入图片描述

C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

4.经典笔试题

4.1 题目1

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char *p) {p = (char *) malloc(100);
}void Test(void) {char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main() {Test();return 0;
}

请问运行Test 函数会有什么样的结果?

运行Test函数会导致未定义行为。

GetMemory函数中,传入的char *p是一个局部变量,当在函数内部对其进行修改,并不会影响到原始调用函数中的指针。这是因为函数的参数是通过值传递的,即函数得到的是实参的副本,对参数的修改不会影响原始的实参。

Test函数中,将一个NULL指针str传递给GetMemory函数,然后在GetMemory函数中分配了内存并将新的地址赋给p。但这对str并没有影响,str仍然是一个NULL指针,指向未分配的内存。

接着,在Test函数中使用strcpy将字符串拷贝到str指向的内存,但是str指向的内存并没有被分配,这将导致未定义行为。

为了正确地分配内存并使用指针,需要修改GetMemory函数,使其返回分配的内存地址,并在Test函数中接收返回的指针。另外,别忘了在使用完内存后,需要使用free函数来释放动态分配的内存。

改写1:

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char **p) {*p = (char *) malloc(100);
}void Test(void) {char *str = NULL;GetMemory(&str);   //传指针的地址strcpy(str, "hello world");printf(str);//释放free(str);str = NULL;
}int main() {Test();return 0;
}

改写2:

#include <stdio.h>
#include <stdlib.h>
char *GetMemory() {char *p = (char *) malloc(100);return p;
}void Test(void) {char *str = NULL;str = GetMemory();   //接受返回的pstrcpy(str, "hello world");printf(str);//释放free(str);str = NULL;
}int main() {Test();return 0;
}

4.2 题目2

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

请问运行Test函数会有什么样的结果?

GetMemory函数中,定义了一个局部数组char p[] = "hello world";,然后将该数组的地址返回给调用者。但是,一旦GetMemory函数执行完毕,其局部变量(p数组)将被销毁,因为它是一个自动存储类别的局部变量。所以,返回的指针指向的是已经无效的内存。

Test函数中,你将GetMemory的返回值赋给指针str,然后使用printf打印str指向的内容。由于GetMemory返回的是一个无效的指针(指向已经被销毁的局部数组),printf可能会打印出垃圾值,或者程序崩溃,或者导致其他不可预测的结果。

这个问题被称为"悬挂指针"问题,因为指针悬挂在指向已经无效的内存位置上。

要解决这个问题,可以考虑使用动态内存分配来分配存储字符串的内存,并在使用完后记得使用free来释放内存。

修改后的代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>char *GetMemory(void) {char *p = (char *)malloc(strlen("hello world") + 1);if (p != NULL) {strcpy(p, "hello world");}return p;
}void Test(void) {char *str = NULL;str = GetMemory();if (str != NULL) {printf("%s\n", str);free(str); // 释放内存}
}int main() {Test();return 0;
}

4.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(str);
}

请问运行Test函数会有什么样的结果?

没有释放内存,导致内存泄漏

修改后的代码实例:

void GetMemory(char **p, int num) {*p = (char *)malloc(num);
}void Test(void) {char *str = NULL;GetMemory(&str, 100);if (str != NULL) {strcpy(str, "hello");printf("%s\n", str);free(str); // 释放内存}
}

4.4 题目4

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

请问运行Test 函数会有什么样的结果?

str被提前释放,再次访问str会导致野指针行为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/11795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理NLP介绍——NLP简介

目录 内容先进性说明内容大纲概要云服务器的使用 内容先进性说明 内容大纲概要 云服务器的使用

githack的安装步骤+一次错误体验

一.githack的安装步骤 1.要在Kali Linux上安装GitHack工具&#xff0c;您可以按照以下步骤操作&#xff1a; 打开终端并使用以下命令克隆GitHack存储库&#xff1a; git clone https://github.com/lijiejie/GitHack.git2.进入GitHack目录&#xff1a; cd GitHack3.安装依赖项…

一种分解多种信号模式非线性线性调频的方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Elasticsearch

文章目录 分布式搜索引擎elasticsearch介绍elasticsearch作用ELK技术栈elasticsearch和lucene 倒排索引正向索引倒排索引正向和倒排比较 es的一些概念文档和字段索引和映射mysql与elasticsearch elasticsearch安装部署单点es部署kibana安装IK分词器扩展词词典停用词典 索引库操…

Go语言基础语法八万字详解,对小白友好

基本语法——变量var 变量的使用 什么是变量 变量是为存储特定类型的值而提供给内存位置的名称。在go中声明变量有多种语法。 所以变量的本质就是一小块内存&#xff0c;用于存储数据&#xff0c;在程序运行过程中数值可以改变 声明变量 var名称类型是声明单个变量的语法…

PaddleOCR #PP-OCR常见异常扫雷

异常一&#xff1a;ModuleNotFoundError: No module named ‘tools.infer’ 实验案例&#xff1a; PaddleOCR #使用PaddleOCR进行光学字符识别&#xff08;PP-OCR文本检测识别&#xff09; 参考代码&#xff1a; 图片文本检测实验时&#xff0c;运行代码出现异常&#xff1a;M…

【我们一起60天准备考研算法面试(大全)-第二十七天 27/60】【真分数】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

8.6 PowerBI系列之DAX函数专题-非日期类型的累计聚合

需求 需求1&#xff1a; 需求2&#xff1a; 实现 1.需求1实现&#xff1a; &#xff08;1&#xff09;在power query中添加列-添加索引列&#xff1b; &#xff08;2&#xff09;根据索引列进行累加计算。 度量值 累计聚合销售额 var current_pro_type selectedvalue(…

CHD6.2.1集群 Hive开启Iceberg

下载jar包 https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-hive-runtime/1.0.0/iceberg-hive-runtime-1.0.0.jar 存放在/opt/cloudera/parcels/CDH/lib/hive/auxlib/ CDH集群修改hive配置 选择xml格式 粘贴即可 <property><name>iceberg.engine.hi…

华为认证HCIA-HCIP-HCIEdatacom题库解析+机构视频+实验

题库包含有2023年最新HCIA-datacom题库、HCIP-datacom题库&#xff0c;HCIE-datacom题库&#xff0c; 云计算HCIA&#xff0c;HCIP题库&#xff0c;云服务HCIA&#xff0c;HCIP题库&#xff0c;华为存储HCIP题库&#xff0c;华为安全HCIP题库 &#xff0c;学习笔记&#xff0c;…

MES管理系统中设备管理功能的原理是什么

制造执行系统MES是一种应用于制造工厂的实际操作系统&#xff0c;它通过实时监控和控制生产流程&#xff0c;为生产过程提供全面的管理和优化。在MES管理系统解决方案中&#xff0c;设备管理功能是非常重要的一部分&#xff0c;它可以实现设备实时监控、故障预警、维护保养等功…

【论文阅读】通过解缠绕表示学习提升领域泛化能力用于主题感知的作文评分

摘要 本文工作聚焦于从领域泛化的视角提升AES模型的泛化能力&#xff0c;在该情况下&#xff0c;目标主题的数据在训练时不能被获得。本文提出了一个主题感知的神经AES模型&#xff08;PANN&#xff09;来抽取用于作文评分的综合的表示&#xff0c;包括主题无关&#xff08;pr…

【打卡】Datawhale暑期实训ML赛事

文章目录 赛题描述任务要求数据集介绍评估指标 赛题分析基于LightGBM模型Baseline详解改进baseline早停法添加特征 赛题描述 赛事地址&#xff1a;科大讯飞锂离子电池生产参数调控及生产温度预测挑战赛 任务要求 初赛任务&#xff1a;初赛提供了电炉17个温区的实际生产数据&…

使用win10专业版自带远程桌面公司内网电脑

在现代社会中&#xff0c;各类电子硬件已经遍布我们身边&#xff0c;除了应用在个人娱乐场景的消费类电子产品外&#xff0c;各项工作也离不开电脑的帮助&#xff0c;特别是涉及到数据采集和储存的场景&#xff08;如安保监控、自动化流程等等&#xff09;&#xff0c;更是离不…

pytest自动化测试指定执行测试用例

1、在控制台执行 打开cmd,进入项目目录 指定执行某个模块 pytest testcases\Logistics\Platform\CarSource\test_CarSourceList.py 指定执行某个目录及其子目录的所有测试文件 pytest testcases\Logistics\Platform\CarSource 指定执行某个模块的某个类的某个测试用例 pyte…

音视频入门之音频采集、编码、播放

作者&#xff1a;花海blog 今天我们学习音频的采集、编码、生成文件、转码等操作&#xff0c;我们生成三种格式的文件格式&#xff0c;pcm、wav、aac 三种格式&#xff0c;并且我们用 AudioStack 来播放音频&#xff0c;最后我们播放这个音频。 使用 AudioRecord 实现录音生成…

文件系统总结

《本文件系统默认linux文件系统》 一、文件系统基本概念 文件系统是操作系统中负责存取和管理信息的模块&#xff0c;它用统一的方式管理用户和系统信息的存储、检索、更新、共享和保护&#xff0c;并为用户提供一整套方便有效的文件使用和操作方法文件系统是操作系统中管理文…

idea terminal npm指令无效

文章目录 一、修改setting二、修改启动方式 一、修改setting 菜单栏&#xff1a;File->Settings 二、修改启动方式 快捷方式->右键属性->兼容性->勾选管理员身份运行

unittest 数据驱动DDT应用

前言 一般进行接口测试时&#xff0c;每个接口的传参都不止一种情况&#xff0c;一般会考虑正向、逆向等多种组合。所以在测试一个接口时通常会编写多条case&#xff0c;而这些case除了传参不同外&#xff0c;其实并没什么区别。 这个时候就可以利用ddt来管理测试数据&#xf…

WEB APIs day4 (2)

三、M端事件 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, …