首页 > 试题广场 >

如果两段内存重叠,用 memcpy 函数可能会导致行为未定义

[单选题]
补充下面函数代码:
如果两段内存重叠,用 memcpy 函数可能会导致行为未定义。 而 memmove 函数能够避免这种问题,下面是一种实现方式,请补充代码。
#include <iostream>
using namespace std;

void *memmove(void *str1, const void *str2, size_t n) {
    char *pStr1 = (char *)str1;
    char *pStr2 = (char *)str2;
    if () {
        for (size_t i = 0; i != n; ++i) {
            *(pStr1++) = *(pStr2++);
        }
    } else {
        pStr1 += n - 1;
        pStr2 += n - 1;
        for (size_t i = 0; i != n; ++i) {
            *(pStr1--) = *(pStr2--);
        }
    }
    return ();
}

  • pStr1 < pStr2       str1
  • pStr1+n < pStr2       str2
  • pStr1+n < pStr2 || pStr2+n < pStr1       str2
  • pStr2+n < pStr1       str1
推荐

黑色曲线表示错误的拷贝方式,红色的曲线表示正确的拷贝方式。
黑色方块表示的是二者内存覆盖的区域
与 pstr*+n 没有关系
第一中选择分支对应的是图 1 中红色曲线的拷贝方式 (方向: pstr2 -> pstr1)

第二中选择分支  else ... 对应的是图 2 中红色曲线的拷贝方式
方向是 pstr2 -> pstr1
编辑于 2015-06-23 14:42:36 回复(8)
第一个填pStr1< pStr2,第二个填str1。1、 如果第一个填的是选项D的pStr2+n<pStr1,即当 pStr2+n>=pStr1时,进行后拷贝。若此时pStr2-n<pStr1<pStr2,即pStr2<=pStr1+n-1<pStr2+n-1,当后拷贝(把pStr2+n-1拷贝到pStr1+n-1)时就会覆盖pStr2中的pStr2~pstr2+n-2中其中字节的数据。2、应该返回str1而不是pStr1,因为pStr1的值会在拷贝中改变,可能不是拷贝之后的数据的首地址,而str1始终存储首地址。
发表于 2015-02-17 16:32:24 回复(0)
我想问一下 返回str2有意义吗?
发表于 2020-08-29 16:52:55 回复(0)
int main(){
     char str1[] = "abcdefg";
     char str2[] = "abcdefg";
     //memcpy(str1, str1+2, 4);
     //memmove(str2, str2+2, 4);
     memcpy(str1+2, str1, 4);
     memmove(str2+2, str2, 4);
     cout << str1 << endl;
     cout << str2 << endl;
 
     return 0;
 }
为什么memcpy和memmove没有差别呢。谁能解释下。memcpy不是应该有覆盖的吗。
编辑于 2015-10-03 16:32:36 回复(1)
这题目其实挺简单的,但还是上当了。
发表于 2015-03-31 08:29:21 回复(0)
pStr1< pStr2  选a没错 但是源码是  pStr1<= pStr2 的
发表于 2015-03-25 20:44:17 回复(0)
为什么大家都在纠结重叠这个问题呢?和充不重叠没关系,利用题目中这种方法就可以避免讨论是否重叠的问题了。
发表于 2015-03-16 10:52:55 回复(0)
答案为A,如果pstr1<pstr2,则从前面开始copy。如果否则,就从后面copy
发表于 2015-03-09 19:45:24 回复(0)
答案为A,如果pstr1<pstr2,则从前面开始copy。如果否则,就从后面copy
发表于 2015-02-12 09:46:36 回复(0)
原型:void *memmove( void* dest, const void* src, size_t count );
头文件:<string.h>
功能:由src所指内存区域复制count个字节到dest所指内存区域。

如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。 

结合高票答案的图:
pStr1< pStr2时,即 pStr1的首地址小于   pStr2的首地址(图1),正序拷贝
pStr1> pStr2时,即 pStr1的首地址大于   pStr2的首地址(图2),倒叙拷贝
编辑于 2017-05-02 19:18:08 回复(0)
#include <iostream>
#include <string.h>
using namespace std;

//实现memmove库函数
void* memmove(void *dst, const void *src, size_t count){
    // 容错处理
    if(dst == NULL || src == NULL){
        return NULL;
    }
    unsigned char *pdst = (unsigned char *)dst;
    const unsigned char *psrc = (const unsigned char *)src;
    //判断内存是否重叠
    bool flag1 = (pdst >= psrc && pdst < psrc + count);
    bool flag2 = (psrc >= pdst && psrc < pdst + count);
    if(flag1 || flag2){
        // 倒序拷贝
        while(count){
            *(pdst+count-1) = *(psrc+count-1);
            count--;
        }//while
    }
    else{
        // 拷贝
        while(count--){
            *pdst = *psrc;
            pdst++;
            psrc++;
        }//while
    }
    return dst;
}

int main(){
    // 内存重叠
    char c1[] = "hello world";
    memmove(c1+3, c1, 8);
    cout<<"memmove result:"<<c1<<endl;
    // 内存不重叠
    char c2[] = "hello world";
    char c3[] = "love you";
    memmove(c2,c3,8);
    cout<<"memmove result:"<<c2<<endl;
}

发表于 2015-02-04 10:04:23 回复(1)
本题目的:将str2指向的内容完整的拷贝到str1,不关心str2是否被覆盖。
编辑于 2016-08-19 16:08:39 回复(1)
前面的大佬们图文并茂讲得很好,这里我再补充一下我的个人思路:看参数就知道是pStr2指针给pStr1赋值(一个有const一个没有,这样就可以知道最终一定是返回pStr1,可以马上排除B C选项),最终结果一定是首尾相连(因为题目前提就是内存重叠)那么这种首尾相连可以分为两种情况:
(1)是pStr1的尾部与pStr2首部相连(即pStr1<pStr2)此时从头到尾赋值,最终会把pStr2的首部覆盖掉但是我们想要返回的是pStr1所以无所谓
(2)是pStr1首部与pStr2的尾部相连(即pStr1>pStr2)如果是从头到尾开始赋值那么一开始pStr1的头部数据就会覆盖掉pStr2尾部的数据,造成数据丢失,所以需要用从尾部往前赋值的方式,最终pStr1的头部依旧是覆盖掉pStr2尾部的数据但是此时pStr2尾部的数据已经被读取了也就无所谓了。

此外还有一个小技巧,题目中说内存重叠,但是我们看选项B C D的pStr1+n<pStr2等等,一看就知道两个地址之间相差超过n了,是不会出现内存重叠的了,所以马上就可以选择A了
发表于 2018-07-29 16:31:08 回复(1)
A
【解析】目标地址小于源地址时,可以从前向后拷贝
编辑于 2015-06-23 14:42:26 回复(1)
(1)内存低端 <-----s-----> <-----d-----> 内存高端 start at end of s 
(2)内存低端 <-----s--<==>--d-----> 内存高端 start at end of s 
(3)内存低端 <-----sd-----> 内存高端 do nothing 
(4)内存低端 <-----d--<==>--s-----> 内存高端 start at beginning of s  
(5)内存低端 <-----d-----> <-----s-----> 内存高端 start at beginning of s 
  
memmove用于从src拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。 
void * __cdecl memmove ( void * dst,const void * src,size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count))
{
// 若dst和src区域没重叠,则从起始处开始逐一拷贝
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else
{ // 若dst和src 区域交叉,则从尾部开始向起始位置拷贝,这样可以避免数据冲突
dst = (char *)dst + count - 1;
src = (char *)src + count - 1;
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
答案A没错,因为它目的达到了。
保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中
 
编辑于 2015-05-05 10:01:57 回复(0)
D选项也是对的,
str2 + n < st1, 当条件满足时表示没有重叠区域,
所以应该 
if( str1 < str2 || str2 + n < st1)

发表于 2021-05-24 10:44:00 回复(2)
D
首先第一个判断,应该判断两个指针所指的内存区是否有重叠,如果没有重叠,则从头开始一字节一字节的拷贝,如果有重叠,则选择从后向前拷贝。
发表于 2015-01-18 23:56:04 回复(1)
保证从重叠部分开始复制。
发表于 2020-11-18 13:07:36 回复(1)
其实就是两种情况,
str1 < str2的时候,从str2直接拷贝n个字符到str1
str2<=str1的时候,从str2的最后一位往前拷贝n个字符到str1(从str1+n到str1),因为如果存在内存重叠,只有从后向前拷贝才能保证str2的数据不被覆盖,画图即能很快看出来
发表于 2015-05-21 00:03:34 回复(1)

参数:

  • 第一个参数是目标内存块的起始地址dest
  • 第二个参数是源内存块的起始地址src

思路:先拷贝重合部分,防止发生覆盖

  • dest < src, 目标内存块的尾部和源内存块的头部重合,从源内存块的起始位置开始拷贝
  • dest > src, 目标内存块的头部和源内存块的尾部重合,从源内存块的末尾位置开始拷贝
发表于 2024-08-05 19:28:52 回复(0)
首先判断返回的是谁,是str1,依据有:1.都是str2给str1赋值,说明str2是被拷贝的值,str1是拷贝的结果;2.传入的参数的形式是常量指针,无法修改其指向的值,str2就不能被改变,更不可以作为结果返回。
以上的推断可以排除B、C。
然后再回到题目,主要解决的问题是拷贝中出现的内存重叠问题,就可以分为两种情况讨论:
1.如果被拷贝的头指针str2在拷贝结果的头指针str1后面(str1<str2),那么直接从头开始拷贝即可,因为随着指针的同时移动,一定有str1<str2,不怕被覆盖;
2.如果被拷贝的头指针str2在拷贝结果的头指针str1前面(str1>str2),为避免覆盖的出现,只需要从后往前拷贝即可。
上面两种情况对应函数中的if和else,结果选A
编辑于 2024-03-05 16:37:18 回复(0)