菜鸟笔记
提升您的技术认知

使用memset踩过的坑

使用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类似的问题。