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

使用memset踩过的坑-ag真人游戏

库函数 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 
#include 
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(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,避免内存泄漏;
  • 在使用提供的memcpy()、strcpy()、memmove()时也应注意与memset类似的问题。

实质是对内存的单个字节进行操作;

  • 注意使用memset对象本身在系统中占有的字节大小,特别是进行非零赋值时;
  • 使用memset时尽量使用sizeof()关键字进行长度计算;
  • 针对含有指针类型的复杂数据类型,谨慎使用memset,避免内存泄漏;
  • 在使用提供的memcpy()、strcpy()、memmove()时也应注意与memset类似的问题。
网站地图