STL C++ string类不能使用memcpy,memset等一序列内存操作

前言memset,memcpy

在C语言中,经常需要对内存进行操作,里面涉及到很多函数,但是memset函数的使用需要注意。
函数原型: void *memset(voidd *s, int ch, size_t n);
函数功能是:将s所指向的某一块内存中的前n个字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向s的指针,它是对较大的结构体或数组进行清零操作的一种最快方法。
头文件: <memory.h>或者<string.h>

memset函数通常用来对已经分配好地址的内存进行初始化,并且通常初始化为0或者’\0’,(实际上是一样的)。


注意:

  • memset中的第三个参数一定要使用sizeof
  • memset的第一个参数一定是已知的、已经被分配内存的地址
  • 大家可能比较疑惑,memset的第一个参数已经有了被初始化空间的首地址,为什么还要返回一个void*的指针去指向这个地址呢?这种结构在很多函数库里面比较常见,比如字符串操作函数等,都有类似的现象,这里之所以还要返回这个指针是为了实现链式编程,所谓链式编程,举个例子大家就明白了。
    链式编程:
    memcpy(cBuf1, memset(cBuf, 'a', sizeof(char) * 10), sizeof(char) * 10);
    缺点:链式编程让代码显得不直观!!!

  • 最后一点,也是最重要的一点:
    memset是按照字节字节进行赋值的
    如果对int进行赋值,初始化为1,则会被初始化为0000 0001 0000 0001 0000 0001 0000 0001


一、memcpy,memset等内存操作的使用

对于memcpy函数 ,因为memcpy 执行的是浅拷贝,而String类因为用指针自行管理内存,是不能进行浅拷贝的。比如如下代码会发生访问异常:

std::wstring* pstrGameName = new std::wstring(L”大天使之剑”);
std::wstring* pstrCopyGameName = new std::wstring();

memcpy(pstrCopyGameName, pstrGameName , sizeof(std::wstring));
delete pstrGameName;
pstrGameName = NULL;

delete pstrCopyGameName;
pstrCopyGameName = NULL;

因为这两个string都是在堆区分配的,第一个string(* pstrGameName)在new的时候,会申请一块WCHAR* 数组,第二string在进行memcpy拷贝的时候,由于是浅拷贝,只是把* pstrCopyGameName的WCHAR* 指针指向了第一个string的WCHAR*数组。即两个string内部的WCHAR指针指向了同一块内存区域。但分别delete掉两个string的时候 ,会对同一块WCHAR数据区域释放两次,所以会造成访问冲突,发生崩溃。

所以从这里要养成一个好的编程习惯,就是在定义struct结构体的时候,成员最好不要使用string类,而是用WCHAR数组来代替。同时不要使用memcpy函数,而是自己重装结构体的复制操作符,通过赋值操作符来对struct进行复制。

以下方式不推荐使用:

struct ClockInfo
{
std::wstring strGameName;
};

以下方式推荐使用:

struct ClockInfo
{
  WCHAR szGameName[1024];
  ClockInfo()
   {
    memset(szGameName, 0 , sizeof(szGameName));
   }
  ClockInfo& operator=(const ClockInfo& cInfo)
   {
     if(&cInfo != this)
      {
       memcpy(this->szGameName, cInfo.szGameName, sizeof(this->szGameName));
      }
     return *this;
   }
};

二、浅拷贝 深拷贝

浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个类,没有提供复制构造函数,当用该类的一个对象去给另一个对象赋值时所执行的过程就是浅拷贝,如:

class A
{
  public:
     A(int _data):
        data=_data;
     A(){}
  private:
     int data;
};
int main()
{
 A a(5);
 A b=a;     //仅仅是数据成员之间的赋值
 //这一句b=a;就是浅拷贝,执行完这句后b.data=5;
}

- 如果对象中没有其他的资源(如:堆,文件,系统资源等),则 深拷贝和浅拷贝没有什么区别,但当对象中有这些资源时,例子:

 class A 
 {
   public:
        A(int _size)
        {
           size = _size;
           data = new int[size]; // 假如其中有一段动态分配的内存 
        }
       A(){};
       ~A()
       {
           delete [] data;
       } // 析构时释放资源 
     private:
        int* data;
        int size; 
};
int main()
 {
   A a(5);
   A b = a; // 注意这一句 
}

这里的b = a会造成未定义行为,因为类A中的复制构造函数是编译器生成的,所以b = a执行的是一个浅拷贝过程。我说过浅拷贝是对象数据之间的简单赋值,比如:

     b.size=a.size;
     b.data=a.data;
     /*这里b的指针data和a的指针指向了堆上的同一片内存,
     a和b析构时,b先把其data指向的动态分配的内存释放了一次,
     而后a析构时又将这块已经被释放的内存再释放一次。
     对同一块动态内存执行2次以上释放的结果是未定义的,所以这将导致内存泄漏或程序崩溃。
     */

这种情况就要用深拷贝来解决这个问题,深拷贝指的是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值。

例子如下:

class A
{
   public:
       A(int _size)
       {
         size=_size;
         data=new int[size];
         //假如其中有一段动态分配的内存
       }
   A(){};
   A(const A& _A)    //深拷贝
   {
     size=_A.size;
     data=new int[size];
   }
   ~A(){delete [] data;}   //析构时释放资源
};
int main()
{
  A a(5);
  int b=a; //这次就没有问题了!!!
}


  • 总结:
    深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。

三、待续

猜你喜欢

转载自blog.csdn.net/weixin_39540045/article/details/80493764