前言
memset 作为对内存初始化的函数,还是有不少坑和误区的,今天就来对这个函数作一个总结。
一、函数作用
memset 函数在 C++ 中被广泛应用于内存的初始化和设置。它可以将一段连续的内存空间快速设置为指定的值。这个函数主要作用于数组、结构体等数据类型,使其初始状态满足特定的要求。
最简单的调用是将一个数组清零,代码示例如下:
const int maxn = 1024; int a[maxn]; memset(a, 0, sizeof(a)); // a[0]=a[1]=a[...]=0;
这里,sizeof(a) = maxn * 4 = 4096;
表示的是将 数组首地址 a 开始往后的 4096 个字节,都设置为 0;
换句话说,通过 memset 函数,我们可以将数组 a 的所有元素都初始化为 0。这种操作在处理大量数据时,可以大大提高编程效率。
二、效率对比
直接调用 memset 接口清零 和 调用循环进行清零,进行一个测试后如下:
对长度为 10000000 的数组,执行100次调用;
因为 release 版本会做各种优化,编译器发现重复执行无效逻辑就会跳过,所以不太好造数据测试,研究时间效率的时候还是参考 debug 版本(当然,软件发布的时候肯定用的是 release 版本)。
memset 无论从时间效率,还是代码整洁来看都是由于 for 循环的,当然也带来了一些容易引起误解的地方。
三、误区总结
1、按字节设置
memset 实现原理是根据字节来设置的,比如对于字节数组 char a[100],将所有字节都设置为5,就可以调用:
memset(a, 5, sizeof(a));
但是,对于 int b[100],也采用这种方法,就会导致错误:
memset(b, 5, sizeof(b));
得到 b 数组中元素的值为 84215045;
为什么呢?
我们把这个数组转换成二进制,得到:
因为 int 占据了 4 个字节,把每个字节都设置成了 5,所以最后转成十进制就变成了 84215045;
同理,当类型是 short(二字节整数),或者 long long(八字节整数)都会有类似问题,总结表格如下:
表格中,只有0 和 -1是正常的,因为 0 的二进制表示中,所有位都为0;-1 的二进制表示中,所有位都为 1;
特别的,当需要设置的数,对应类型的每个字节都是同一个数的时候,也可以采用 memset,比如:int 类型的 252645135(十六进制表示为:0x0f0f0f0f);
2、设置的值只有最低字节有效
memset(a, 0x05ffffff, sizeof(a)); memset(a, 0xffffff05, sizeof(a)); memset(a, 0xffffff08, sizeof(a)); memset(a, 0x12345678, sizeof(a));
设置值的时候,只会采用最低的字节作为赋值用,通俗的讲,就是以上四句话调用,等价于:
memset(a, 0xff, sizeof(a)); memset(a, 0x05, sizeof(a)); memset(a, 0x08, sizeof(a)); memset(a, 0x78, sizeof(a));
3、堆内存不可直接 sizeof 取首地址
在 C++ 中,当我们在堆上申请内存时,通常使用 new 关键字。当我们使用 sizeof 运算符对一个指针进行操作时,返回的将是指针本身的大小,而不是它所指向的内存的大小。因此,当我们需要对一个数组或结构体指针进行初始化或内存设置操作时,我们需要正确计算内存大小。以下是关于堆内存和数组参数传递时的一些建议和注意事项。
1.在堆上申请内存时,使用 new[] 操作符来创建一个动态数组。这将返回一个指向数组第一个元素的指针。例如:
const int maxn = 1024; int *p = new int[maxn];
2.当需要对动态数组进行初始化或内存设置操作时,使用 sizeof 运算符与数组元素类型的大小相乘,以获取正确的内存大小。例如:
const int maxn = 1024; int *p = new int[maxn]; memset(p, 0, maxn * sizeof(int));
4、传参数组不可直接 sizeof 取首地址
1.当将数组作为函数参数传递时,数组名会退化为指针。因此,在函数内部,我们不能使用 sizeof 运算符直接获取数组大小。相反,我们需要将数组大小作为函数参数传递,或者使用其他方法来获取数组大小。例如:
void fun(int a[], int size) { memset(a, 0, size * sizeof(int)); }
2.当结构体指针作为函数参数传递时,同样需要注意这个问题。结构体指针会退化为指针,因此我们不能使用 sizeof 运算符直接获取结构体的大小。解决方法与数组类似,将结构体大小作为函数参数传递,或者使用其他方法获取结构体大小。例如:
void fun(MyStruct *s, int size) { memset(s, 0, size * sizeof(MyStruct)); }
总之,在处理堆内存和数组参数传递时,我们需要注意指针和数组名之间的差异,并使用正确的方法获取内存大小,以避免错误和潜在的内存问题。