慎用memset

C/C++的内存初始化函数memset函数一定要慎用

memset是一个极其方便的函数,其主要功能是初始化一段内存区域,来自于头文件<string,h>,但是这个函数也是一个相对而言很容易出错的函数,让我们先来看一下他的解释:

C++ Reference对于memset的定义为:

Fill block of memory
Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).

void memset ( void ptr, int value, size_t num );

ptr: Pointer to the block of memory to fill.
value: Value to be set. The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value.
num:Number of bytes to be set to the value.

解释一下其中的三个参数:

Ptr表示一个指针,其指向一段内存
Value表示填充这个区域内存的值
Num表示这段内存区间

这里可以注意一下,我们通常使用sizeof进行num段的表示,sizeof可以快速的获取到所需空间的字节大小,这通常是与类型所决定的。
比如说我们可以给一个数组初始化为0,测试代码如下:

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=20;
int a[maxn];
int main(){
cout<<"memset 使用前:"<<endl;
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl<<"memset 使用后:"<<endl;
memset(a,0,sizeof(a));
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl;
return 0;
}

结果为:
memset 使用前:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
memset 使用后:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

这样乍一看没什么问题,而且我们的数组在创建的同时自己就已经是一个赋值为0的情况下了,所以这样的操作没有任何问题,我们再拿字符数组进行一下测试,修改代码如下:

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=20;
char a[maxn];
int main(){
cout<<"memset 使用前:"<<endl;
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl<<"memset 使用后:"<<endl;
memset(a,'A',sizeof(a));
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl;
return 0;
}

结果为:
memset 使用前:

memset 使用后:
A A A A A A A A A A A A A A A A A A A A

因为我们的字符数组在空的情况下对于ASCII码表格的空,因此直接进行输出是没有任何东西的,但是我们在使用memset函数进行集体初始化之后他就变得有了值,如本样例他会变成全部是’A’的内容。

接下来就是问题所在了
如果我们想要将其全部初始化一个自定义的值呢?比如说全部初始化为1,又会怎么样呢?
我们修改测试代码如下:

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=20;
int a[maxn];
int main(){
cout<<"memset 使用前:"<<endl;
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl<<"memset 使用后:"<<endl;
memset(a,1,sizeof(a));
for(int i=0;i<maxn;i++){
cout<<a[i]<<' ';
}
cout<<endl;
return 0;
}

其结果为:
memset 使用前:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
memset 使用后:
16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009 16843009

这与我们所求的结果大相径庭,我们所需要赋值的1去哪了?为什么是16843009这个完全不相干的数字在里面,难道我们的代码出错了么?为了测试,我们又将其内容赋值为了2,结果所有的数据变成了33686018这个值,同样与我们所需求的结果不相同。
这是为什么呢

其原因就是出在我们memset的底层设计上,memset是采用每一个字节每一个字节进行赋值的,也就是说,我们所给出的1,其因为int类型占用四个字节的原因被按照内存分为了四个部分,每一个部分存在一个1,因此经过memset函数初始化之后的数组的内容应该成了这个样子(使用二进制进行表示)

0000 0001 0000 0001 0000 0001 0000 0001 共4个字节,每一个字节占用8个二进制位
我们再来计算一下,
为了方便理解,这里不写直接的运算函数,而是使用C++的bitset函数进行测试,我们直接把数据放入bitset函数中,看一下他转换出来的二进制是否与我们之前的预期相同即可。

#include<bits/stdc++.h>
using namespace std;
int main(){
bitset<32> s(16843009);
cout<<s<<endl;
return 0;
}

其结果为:
00000001 00000001 00000001 00000001
(为了方便而直观的表示我稍微用了一下空格做处理,8个二进制一组)

完全一致,因此我们根据这个结论,得出了一个经验:

  1. 对于整形等精度在2字节以上的数据类型,除了赋值为0 (十六级进制0x00)与赋值为-1 (十六级进制0xff)之外的数据,不方便进行直接的初始化的结论。
  2. 对于字符数组这样的单字节数据类型,可以方便的进行初始化
  3. 此外,memset相当于一次从头到尾的遍历,其本身的时间复杂度是O(N)线性级别的,与for()循环遍历整个数组并且依次赋值并没有其他的区别,如果在极其苛刻的时间复杂度条件下这个函数需要慎用。

总之memset必须要慎用,至于我为什么发这个文章,说多了都是泪啊,诶(肥宅大哭)

分类: C++

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注