资深同事教我做人:堆上分配的内存,只free就完事儿了?

2023-12-19 14:32

本文主要是介绍资深同事教我做人:堆上分配的内存,只free就完事儿了?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

malloc要和free成对使用,malloc的内存,free就完事儿了吗?

最近的工作是和另一位资深同事一起,修复组里新老项目上可能存在的一些安全漏洞,包括参数检查和内存释放。参数检查倒还ok,在每个函数开始出对入口参数判空。这一点老代码做得很好,所以这部分工作量很小。关键是内存的释放。 

new之后,delete了吗?malloc之后,free了吗?

准确说,是及时释放堆上动态分配的内存。作为基础知识都知道(而且书上也反复提到过),堆上动态分配的内存,诸如malloc,calloc,在使用完后要释放掉。然而在没养成良好编程习惯或者遵守编程规范的时候,往往只考虑实现功能,而没有考虑过内存泄露。

不禁回想起硕士期间开发的机器人仿真软件,为了论文需要,我专门增加了一个实时显示软件占用内存的模块。实现之后发现,在软件仿真的过程中,占用内存越来越多;反复运行仿真模块时,内存持续增加,巅峰时达到500多M。这就是因为软件里只new却不delete。那时不论是实验室还是项目公司,全部注意都在软件功能实现和炫酷上,所以压根儿不考虑内存问题。

公司里却不一样。一方面,要做成熟的产品,各方面性能需要达到最优;另一方面,资深同事review代码时会查到哪里存在内存泄漏;再者,检测工具也会查到这些内存泄漏点。

堆上内存分配的方式

堆内存是区别于栈区、全局数据区和代码区的另一个内存区域。堆允许程序在运行时动态地申请某个大小的内存空间。

1.mallloc

malloc的全称是memory allocation,用于申请一块连续的指定大小的内存块区域void*类型返回分配的内存区域地址。使用示例如下。调用malloc后需要判断malloc是否成功。

#define BUFFER_SIZE 5int *buffer = (int*)malloc(BUFFER_SIZE * sizeof(int));if (NULL == buffer){printf("malloc failed!\n");return;}

malloc申请的内存在申请后不会初始化,如下代码,打印出刚分配得到的内存块的值,可以看到是未初始化的随机值:

#define BUFFER_SIZE 5int *buffer = (int*)malloc(BUFFER_SIZE * sizeof(int));if (NULL == buffer){printf("malloc failed!\n");return -1;}for (int i = 0; i < BUFFER_SIZE; i++){printf("%d \t", buffer[i]);}

为避免在开辟到内存块后在未经初始化时就误用到这块内存,更好的方法是在malloc后将该内存块初始化为0(或者指定值)

#define BUFFER_SIZE 5int *buffer = (int*)malloc(BUFFER_SIZE * sizeof(int));if (NULL == buffer){printf("malloc failed!\n");return -1;}memset(buffer, 0, BUFFER_SIZE * sizeof(int));

2.calloc

在内存的动态存储区中分配num个长度为size的连续空间,函数返回一个指向分配起始地址的void*指针;如果分配不成功,返回NULL。

	// void* calloc(unsigned int num,unsigned int size)int *buffer2 = (int*)calloc(BUFFER_SIZE, sizeof(int));if (NULL == buffer2){printf("calloc failed!\n");return -1;}

calloc申请的内存在申请后会被初始化为0,所以可以不必再次初始化为全0。

3.realloc

realloc函数的功能比malloc函数和calloc函数的功能更为丰富,可以实现内存分配和内存释放的功能,其函数声明如下:

	//   para:  p-堆上已经存在空间的地址//   para:  n-空间的大小//void * realloc(void * p, int n);

其中,指针p必须为指向堆内存空间的指针,即由malloc函数、calloc函数或realloc函数分配空间的指针。realloc函数将指针p指向的内存块的大小改变为n字节。如果n小于或等于p之前指向的空间大小,则保持原有状态不变。如果n大于原来p之前指向的空间大小,系统将重新为p从堆上分配一块大小为n的内存空间,同时,将原来指向空间的内容依次复制到新的内存空间上,p之前指向的空间被释放relloc函数分配的空间也是未初始化的

4.new

在C++中,new是经常使用的用于动态分配内存的关键字:

	int *temp = new int(5);

malloc后free,完整操作是什么?

malloc/calloc/realloc分配的内存使用完毕后,应该用free释放掉,这还不算完,还应该把指针置为NULL,如下段代码:


#define BUFFER_SIZE 10int *buffer = (int*)malloc(BUFFER_SIZE * sizeof(int));for (int i = 0; i < 10; i++){buffer[i] = i*i;}free(buffer);buffer = NULL;

很多时候会忘记free,或者free了之后忘了置空。还有的时候,是可能在某个过程把一段分配的内存赋给了另一个指针,free了原来的指针后操作另一个指针会有风险。比如下段代码,buffer1和buffer2指向的是同一段内存,如果free了buffer1,其实也相当于free了buffer2了。


void print(char* title, int* buffer, int bufferSize)
{if (title == NULL || buffer == NULL || bufferSize <= 0){printf("Wrong parameters!\n");return;}printf("%s:\n", title);for (int i = 0; i < bufferSize; i++){printf("Address: 0x%p, Value: %d\n", buffer + i, buffer[i]);}printf("\n");
}
int  main()
{
#define BUFFER_SIZE 5int *buffer1 = (int*)malloc(BUFFER_SIZE * sizeof(int));int *buffer2 = buffer1;for (int i = 0; i < BUFFER_SIZE; i++){buffer1[i] = i*i;}print("buffer1", buffer1, BUFFER_SIZE);print("buffer2", buffer2, BUFFER_SIZE);free(buffer1);buffer1 = NULL;print("buffer2", buffer2, BUFFER_SIZE);system("pause");return 0;
}

free并且置为NULL,就完事儿了吗?

这周在开展这一项工作之前,我们先开了个会,会上主管要求我们,free之前先用memset把那段内存清零,然后再free置NULL。理由是,尽管free并置NULL了,但那段内存上的内容可能还在,有被窃取的风险

所以完整的操作如下段代码:


int *buffer1 = (int*)malloc(BUFFER_SIZE * sizeof(int));// Do something on buffer1memset(buffer1, 0, BUFFER_SIZE);
free(buffer1);
buffer1 = NULL;

当然,在memset由calloc分配的内存时,应该设置分配时候的大小,即

memset(buffer, 0, num * size);

free只是告诉编译器,buffer指定的内存可以被重新分配了,但是这段内存上的内容呢?置0?随机值?还是保持原来的值?查了些资料,有说不变的,有说是跟具体编译器相关的,无论如何,看来主管要求的这一步,是很有必要的!

关于malloc分配内存,还有一个tip

同事在review代码时,提示在为一个带有指针变量的结构体malloc一段内存后,如果这个结构体变量要作为传入的参数,在使用之前,应该先memset为0,否则里面的指针值是随机或者未知的,作为传入参数,可能有风险

这篇关于资深同事教我做人:堆上分配的内存,只free就完事儿了?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/512557

相关文章

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存

理解java虚拟机内存收集

学习《深入理解Java虚拟机》时个人的理解笔记 1、为什么要去了解垃圾收集和内存回收技术? 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。 2、“哲学三问”内存收集 what?when?how? 那些内存需要回收?什么时候回收?如何回收? 这是一个整体的问题,确定了什么状态的内存可以