memset的坑

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


6caa3f690e416a5c314ca693807f0149.png


因为 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;


为什么呢?


我们把这个数组转换成二进制,得到:

d7166f61f1b6382d0ebdd9e077c86085.png



因为 int 占据了 4 个字节,把每个字节都设置成了 5,所以最后转成十进制就变成了 84215045;


同理,当类型是 short(二字节整数),或者 long long(八字节整数)都会有类似问题,总结表格如下:


8dd11b498ac60db279ca0ae612199016.png


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


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



相关文章
|
5月前
|
C语言
sizeof
【6月更文挑战第17天】
63 1
strlen和sizeof详解
strlen和sizeof详解
84 0
|
6月前
|
存储 C语言
关于sizeof介绍与分享给大家介绍
关于sizeof介绍与分享给大家介绍
sizeof和strlen的对比
sizeof和strlen的对比
51 0
|
存储 编译器 C++
C++ sizeof 的介绍
C++ sizeof 的介绍
112 0
sizeof与strlen练习
sizeof与strlen练习
51 0
|
C++
关于sizeof相关注意点
strlen是专门用来求字符串长度的,统计的是’\0’之前出现的字符个数,一定要找到’\0’才能结束,所以如果没有\0可能会存在越界访问问题
98 0