使用memset 踩过的坑
库函数 memset()
memset 函数是内存赋值函数,用来给某一块内存空间进行赋值的;包含在<[string.h]>头文件中,可以用它对一片内存空间逐字节进行初始化;
注意: 该函数是按一个字节一个字节
来给数组或者是结构体赋值。
原型为 :
void *memset(void *s, int v, size_t n);
这里s可以是数组名,也可以是指向某一内在空间的指针;
v为要填充的值;
n为要填充的字节数;
在C++的API函数文档中对该函数的阐述如下:
void * memset ( void * ptr, int value, size_t num );
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`)
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.size_t is an unsigned integral type.
memset()的使用
使用memset()对单字节数据类型参数赋值
例1:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
该demo是memset()函数的最基本的用法,即对一个char类型的数组进行赋值。通过编译输出结果如下:
This is string.h library function
$$$$$$$ string.h library function
例2:
uint8_t buffer[10];
uint8_t uSize;
uSize = 0;
memset(buffer,0,10);
uSize = sizeof(buffer);
memset(buffer,0,sizeof(buffer));
该函数是针对uint8_t类型的数据进行赋值,在上述demo中uSize的值位10;因此表达式memest(buffer,0,10)和memset(buffer,0,sizeof(buffer))的功能是一样的。memset除了能够对内存块初始化,也可以通过memset(buffer,1,sizeof(buffer))类似的操作对内存块设置其他的数值。
注意: 在进行非零操作是,注意赋值的范围。
memset()对非单字节数据类型的变量赋值
例3:
uint16_t i;
uint16_t uBuffer[10];
for(i=0;i<10;i++)
{
uBuffer[i] = 100;
}
memset(uBuffer,0,10);
memset(uBuffer,0,sizeof(uBuffer));
memset(uBuffer,0,sizeof(uint16_t)*10);
i = sizeof(uBuffer);
for(i=0;i<10;i++)
{
uBuffer[i] = 100;
}
memset(uBuffer,255,10);
memset(uBuffer,255,sizeof(uBuffer));
memset(uBuffer,255,sizeof(uint16_t)*10);
memset(uBuffer,256,sizeof(uBuffer));
demo中的7到9行的本意都是对uBuffer变量进行赋0操作。但是程序实际跑出来输出的效果可能和原来的初衷不同。
首先代码memset(uBuffer,0,10)中设置10表面上看和uBuffer数组大小相等,表面上通过改代码可以将uBuffer数组全部设置成0;但实际上并不是这样,代码执行后uBuffer[0]~uBuffer[4]的值的确是被设置成0,但uBuffer[5]~uBuffer[9]的值是没变的。why? 其原因是memset在对内存操作是以一个字节为单位进行的,长度为10,即对10个字节进行置0操作,而uint16_t类型的数据在内存中是占两个字节大小的,即只完成了5个uint16_t数据类型的操作。
其次,通过sizeof(uBuffer)代码中返回的值也可以看出,整个uBuffer数组的长度其实位20。在内存中uBuffer数组大致的存在方式如下图:
memset(uBuffer,0,sizeof(uBuffer))
和 memset(uBuffer,0,sizeof(uint16_t)*10)
是将uBuffer数组全部赋值为0; 其中sizeof(uint16_t)*10是先获取单个uint16_t的单元大小,然后扩展成整个uBuffer的大小。
memset(uBuffer,255,10)
是将uBuffer[0]~uBuffer[4]所在内存赋值为0xFF,即uBuffer[0]的值位0XFFFF,即uBuffer[0] = 65535。而其余的uBuffer[5]~uBuffer[9]的数值保持不变。
memset(uBuffer,255,sizeof(uBuffer))
和memset(uBuffer,255,sizeof(uint16_t)*10)
将所有的uBuffer数组的数据都设置为65535。
memset(uBuffer,256,sizeof(uBuffer))
代码的意图是将256赋值给uBuffer数组中的每一个元素,但真实情况去不是,该代码存在两个问题首先256并不能真实赋值给uBuffer数组中的每一个元素,其次即时能够赋值给数组中的每个成员,256这个数值也不能赋值给uBuffer。该代码的运行后的结果是uBuffer里面的数组全部为零。原因如下256的十六进制位0x100,而memset中需要填充的值在API中的参数说明中有明确规定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
。
```c
char cBuf[10];
char *p = cBuf;
memset(cBuf, 0, sizeof(cBuf));
memset(p, 0, sizeof(str));
memset(cBuf, 0, sizeof§);
memset(cBuf,0,sizeof(*p));
//只能写sizeof(cBuf), 不能写sizeof§
“`
memset(cBuf, 0, sizeof(cBuf))和memset(p, 0, sizeof(str))
将cBuf中的数据设置为零,而memset(cBuf, 0, sizeof§)只能将cBuf数组中的前四个设置为0,原因是因为sizeof()指针变量,其结果位4,虽然p也指向数组的起始地址,但该代码并不能实现对数组全部赋值为零。memset(cBuf,255,sizeof(*p))只能将cBuf[0]设置为零。原因是sizeof(*p)的长度为1;
针对其他数据类型的变量,其规则和uint16_t一致。
memset()对结构体的变量赋值
struct sData_t
{
uint16_t year;
uint8_t month;
uint8_t day;
};
一般对于结构体进行清空的方式有以下几种方式:
在一个变量一个变量的清空:
sData_t sData;
sData.year =0;
sData.month =0;
sData.day =0;
直接用memset()清空:
memset(&sData,0,sizeof(sData_t));
针对一般的结构体中可以采用上述的memset()进行清空操作,但对于结构体中含有指针类型的变量时,还是建议使用一个个半两单独赋值。
struct sData {
int x;
int* p;
};
sData sDataPar;
sDataPar.p = new char[10];
memset(&sDataPar, 0, sizeof(sDataPar));
当memset初始化时,并不会初始化p指向的char数组单元的值,而会把已经分配过内存的p指针本身设置为0,造成内存泄漏。同理,对std::vector等数据类型,显而易见也是不应该使用memset来初始化的。
除了对结构体内含有指针的结构体在使用memset的时候要注意,针对当结构体或类的本身或其基类中存在虚函数时,也需要谨慎使用memset
例如:
class BaseClass
{
public:
virtual void Test() {}
};
class MyClass : public BaseClass
{
public:
int data[3];
int buf[3];
};
MyClass myClassPar;
memset(&myClassPar, 0, sizeof(myClassPar));
BaseClass* pPar = &myClassPar;
//......
MyClass* my = dynamic_cast<MyClass*>(pars);
上述代码当运行到dynamic_cast时发生异常。原因其实就是在使用memset,使用memset目的是为了初始化数据结构MyClass里的data和buf,一般来说需要初始化的内存空间是sizeof(int) * 3 * 2 = 24字节,但是使用memset直接初始化MyClass类型的数据结构时,sizeof(myClassPar)却是28字节,之所以是28个字节是因为C++的多态,C++对有虚函数的对象会包含一个指向虚函数表(V-Table)的指针,当使用memset时,会把该虚函数表的指针也初始化为0,而dynamic_cast也使用RTTI(Run-Time Type Identification)技术,运行时会使用到V-Table,可此时由于与V-Table的链接已经被破坏,导致程序发生异常。
总结
在使用memset时,需要注意一下几方面:
- 注意memset的实质是对内存的单个字节进行操作;
- 注意使用memset对象本身在系统中占有的字节大小,特别是进行非零赋值时;
- 使用memset时尽量使用sizeof()关键字进行长度计算;
- 针对含有指针类型的复杂数据类型,谨慎使用memset,避免内存泄漏;
- 在使用
<string.h>
提供的memcpy()、strcpy()、memmove()时也应注意与memset类似的问题。
实质是对内存的单个字节进行操作;
- 注意使用memset对象本身在系统中占有的字节大小,特别是进行非零赋值时;
- 使用memset时尽量使用sizeof()关键字进行长度计算;
- 针对含有指针类型的复杂数据类型,谨慎使用memset,避免内存泄漏;
- 在使用
<string.h>
提供的memcpy()、strcpy()、memmove()时也应注意与memset类似的问题。