memset的坑

前言

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次调用;

memset的坑_第1张图片

因为 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(八字节整数)都会有类似问题,总结表格如下:

memset的坑_第2张图片

表格中,只有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];

  1. 当需要对动态数组进行初始化或内存设置操作时,使用 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));
}

  1. 当结构体指针作为函数参数传递时,同样需要注意这个问题。结构体指针会退化为指针,因此我们不能使用 sizeof 运算符直接获取结构体的大小。解决方法与数组类似,将结构体大小作为函数参数传递,或者使用其他方法获取结构体大小。例如:
void fun(MyStruct *s, int size) {
    memset(s, 0, size * sizeof(MyStruct));
}

总之,在处理堆内存和数组参数传递时,我们需要注意指针和数组名之间的差异,并使用正确的方法获取内存大小,以避免错误和潜在的内存问题。

本文由mdnice多平台发布

你可能感兴趣的:(C语言杂谈,c++)