如何在C中实现重叠检查memcpy?

问题描述 投票:0回答:3

这是一个学习练习。我试图通过在复制操作开始之前通知用户复制操作将通过还是失败来增强 memcpy。我最大的问题如下。如果我分配两个 100 字节的 char 数组,并且有两个引用每个数组的指针,我如何知道我正在复制哪个方向?如果我将第一个数组中的所有内容复制到第二个数组中,如何确保用户不会覆盖原始数组?

我当前的解决方案将指针的距离与目标数组的大小进行比较。如果两者之间的大小小于我所说的大小,则会发生覆盖。但如果向另一个方向复制怎么办?我只是有点困惑。

int memcpy2(void *target, void *source, size_t nbytes) {
    char * ptr1 = (char *)target;
    char * ptr2 = (char *)source;


    int i, val;

    val = abs(ptr1 - ptr2);
    printf("%d, %d\n", val, nbytes + 0);
    if (val > nbytes) {
        for (i = 0; i < val; i++){
            ptr1[i] = ptr2[i];
        }
        return 0;  /*success */
    }
    return -1; /* error */
}



int main(int argc, char **argv){

  char src [100] = "Copy this string to dst1";
  char dst [20];
  int p;
  p = memcpy2(dst, src, sizeof(dst));

    if (p == 0)
        printf("The element\n'%s'\nwas copied to \n'%s'\nSuccesfully\n", src, dst);
    else
        printf("There was an error!!\n\nWhile attempting to copy the elements:\n '%s'\nto\n'%s', \n Memory was overlapping", src, dst);
    return 0;


}
c unix pointers memory-management
3个回答
13
投票

确定两个内存范围是否重叠的唯一便携式方法是:

int overlap_p(void *a, void *b, size_t n)
{
    char *x = a, *y =  b;
    for (i=0; i<n; i++) if (x+i==y || y+i==x) return 1;
    return 0;
}

这是因为指针与关系运算符的比较是未定义的,除非它们指向同一个数组。实际上,这种比较确实适用于大多数现实世界的实现,因此您可以执行以下操作:

int overlap_p(void *a, void *b, size_t n)
{
    char *x = a, *y =  b;
    return (x<=y && x+n>y) || (y<=x && y+n>x);
}

我希望我的逻辑是正确的;你应该检查一下。如果您想假设可以获取任意指针的差异,您可以进一步简化它。


2
投票

您要检查的是源相对于目标在内存中的位置:

如果源位于目的地之前(即源 < destination), then you should start from the end. If the source is after, you start from the beginning. If they are equal, you don't have to do anything (trivial case).

这里有一些粗略的 ASCII 绘图来可视化问题。

|_;_;_;_;_;_|          (source)
      |_;_;_;_;_;_|    (destination)
            >-----^    start from the end to shift the values to the right

      |_;_;_;_;_;_|    (source)
|_;_;_;_;_;_|          (destination)
^-----<                 start from the beginning to shift the values to the left

在下面非常准确的评论之后,我应该补充一点,您可以使用指针的差异(目标 - 源),但为了安全起见,请事先将这些指针强制转换为 char * 。

在您当前的设置中,我认为您无法检查操作是否会失败。你的 memcpy 原型阻止你进行任何形式的检查,并且根据上面给出的决定如何复制的规则,操作将成功(除了任何其他考虑因素,例如先前的内存损坏或无效指针)。


0
投票

我不相信“尝试通过在复制操作开始之前通知用户复制操作将通过还是失败来增强 memcpy”。是一个结构良好的概念。

首先,memcpy() 没有正常意义上的成功或失败。它只是复制数据,如果它在源阵列外部读取或在目标阵列外部写入,则可能会导致故障/异常,并且它也可能在这些阵列之一外部读取或写入,而不会导致任何故障/异常,而只是默默地损坏数据。当我说“memcpy 可以做到这一点”时,我并不是在谈论 C stdlib memcpy 的实现,而是在谈论具有相同签名的任何函数——它没有足够的信息来执行其他操作。

其次,如果你对“成功”的定义是“假设缓冲区足够大但可能重叠,将数据从源复制到目标,而复制时不会绊倒自己”——这确实是 memmove() 所做的,而且它是总是有可能的。再次强调,不存在“返回失败”的情况。如果缓冲区不重叠,这很容易,如果源与目标的末尾重叠,那么您只需从开头逐字节复制即可;如果源与目标的开头重叠,那么您只需从末尾逐字节复制即可。这就是 memmove() 的作用。

第三,在编写此类代码时,必须非常小心指针算术(包括加法、减法和数组索引)的溢出情况。在

val = abs(ptr1 - ptr2)
中,
ptr1 - ptr2
可能是一个非常大的数字,并且它将是无符号的,因此
abs()
不会对其执行任何操作,并且
int
是存储它的错误类型。只是让你知道.

© www.soinside.com 2019 - 2024. All rights reserved.