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

时间:2012-10-26 22:53:37

标签: c unix pointers memory-management

这是一项学习练习。我试图通过通知用户复制操作在开始之前是通过还是失败来增加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;


}

3 个答案:

答案 0 :(得分:9)

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

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

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

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

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

答案 1 :(得分:2)

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

如果源位于目的地之前(即源&lt; destination),那么您应该从最后开始。如果源是之后,则从头开始。如果他们是平等的,你不必做任何事情(无关紧要)。

以下是一些粗略的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原型会阻止您对此进行任何形式的检查,并且根据上面给出的规则来决定如何复制,操作将成功(除了任何其他考虑因素之外,如先前的内存损坏或无效指针)。

答案 2 :(得分:0)

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

首先,memcpy()在正常意义上不成功或失败。它只是复制数据,如果它在源数组外部读取或在目标数组外部写入,则可能导致错误/异常,并且它也可能在其中一个数组之外读取或写入而不会导致任何错误/异常并且只是静默地破坏数据。当我说“memcpy做到这一点”时,我不只是谈论C stdlib memcpy的实现,而是关于任何具有相同签名的函数 - 它没有足够的信息来做其他事情。

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

第三,在编写这种代码时,必须非常小心指针算法的溢出情况(包括加法,减法和数组索引)。在val = abs(ptr1 - ptr2)中,ptr1 - ptr2可能是一个非常大的数字,并且它将是无符号的,因此abs()将不对其执行任何操作,并且int是错误的类型将它存入。只是你知道。