memmove 和 memcpy处理内存重叠问题以及两个内存之间完美拷贝(经典实例)

1.区别:

memcpy和memmove()都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下:


void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);


他们的作用是一样的,唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。
一、memcpy函数

Memcpy原型:     

void *memcpy(void *dest, const void *src, size_t n);

描述:
        memcpy()函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存区域不能重叠。
返回值:
        memcpy()函数返回指向dest的指针。
二、memmove函数
memmovey原型:

void *memmove(void *dest, const void *src, size_t n);

描述:
       memmove() 函数从src内存中拷贝n个字节到dest内存区域,但是源和目的的内存可以重叠。
返回值:
        memmove函数返回一个指向dest的指针。

从上面的描述中可以看出两者的唯一区别就是在对待重叠区域的时候,memmove可以正确的完成对应的拷贝,而memcpy不能。

内存覆盖的情形有以下两种:

先看memcpy()和memmove()这两个函数的实现:

void* my_memcpy(void* dst, const void* src, size_t n)
{
    char *tmp = (char*)dst;
    char *s_src = (char*)src;
 
    while(n--) {
        *tmp++ = *s_src++;
    }
    return dst;
}

从实现中可以看出memcpy()是从内存左侧一个字节一个字节地将src中的内容拷贝到dest的内存中,这种实现方式导致了对于图中第二种内存重叠情形下,最后两个字节的拷贝值明显不是原先的值了,新的值是变成了src的最开始的2个字节了。

而对于第一种内存覆盖情况,memcpy的这种拷贝方式是可以的。(不甚明白:首先src是const限制只能是读取不能修改,也就是src原来的1、2位置不能存储dst中的4、5,导致dst数据不完成吧?希望读者在下面评论帮我解答疑惑哈,不胜感激)

而memmove就是针对第二种内存覆盖情形,对memcpy进行了改进,改进代码如下:

void* my_memmove(void* dst, const void* src, size_t n)
{
    char* s_dst;
    char* s_src;
    s_dst = (char*)dst;
    s_src = (char*)src;
    if(s_dst>s_src && (s_src+n>s_dst)) {      //-------------------------第二种内存覆盖的情形。
        s_dst = s_dst+n-1;
        s_src = s_src+n-1;
        while(n--) {
            *s_dst-- = *s_src--;
        }
    }else {
        while(n--) {
            *s_dst++ = *s_src++;
        }
    }
    return dst;
}


在第二种内存覆盖的情形下面,memcpy会出错,但是memmove是能正常工作的。

2.memcpy实现两个内存之间的拷贝(经典实例)

 实现_szRecv接收数据拷贝到_szMsgBuf内存中,在_szMsgBuf中对数据进行处理和覆盖。

#define RECV_BUFF_SIZE 10240     
int _lastposition = 0;//接收消息的长度nLen累加
//接收缓冲区
char _szRecv[RECV_BUFF_SIZE] = {};
//第二缓冲区,消息缓冲区
char _szMsgBuf[RECV_BUFF_SIZE * 10] = {};
int recvData(SOCKET _cSock)
{


	// 5 接服务端数据
	int nLen = recv(_cSock, _szRecv, RECV_BUFF_SIZE, 0);//接收服务端的数据
	DataHeader* header = (DataHeader*)szRecv;
	if (nLen <= 0)
	{
		printf("与服务器断开连接,任务结束。\n", _cSock);
		return -1;
	}
	/*将收取的数据拷贝到消息缓冲区,第一个参数:_szMsgBuf是数组,_szMsgBuf+
	_lastposition表示不断向前偏移_lastposition长度,第二参数:接收缓冲区接收数据,第三
	参数:拷贝的长度。*/
	memcpy(_szMsgBuf + _lastposition, _szRecv, nLen);
	//消息缓冲区的数据尾部后移
	_lastposition += nLen;
	//判断消息缓冲区长度大于消息头,防止少包的问题,DataHeader表示:数据头的结构体
	/*struct DataHeader
	{
	short dataLength;
	short cmd;
	};*/
	while (_lastposition >= sizeof(DataHeader))
	{
		DataHeader* header = (DataHeader*)_szMsgBuf;
		if (_lastposition >= header->dataLength)
		{
			int nSize = _lastposition - header->dataLength;
			onNetMessage(header);//解析包头
		    /*第二个参数表示:从0开始偏移,偏移长度是dataLength,从dataLength以后的数
		    据拷贝到_szMsgBuf的0位置。*/
			memcpy(_szMsgBuf, _szMsgBuf + header->dataLength, nSize);
			_lastposition = nSize;
		}
		else
		{
			//剩余数据不够一个完整数据
			break;
		}
	}

}

猜你喜欢

转载自blog.csdn.net/qq_33263769/article/details/88863608