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