【问题标题】:how to implement overlap-checking memcpy in C如何在 C 中实现重叠检查 memcpy
【发布时间】:2012-10-26 22:53:36
【问题描述】:

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


}

【问题讨论】:

  • memcpy 不能保证适用于重叠区域,但 memmove 可以。也许您想改为实现memmove 语义?
  • 这就是 memmove 的用途。找到一些来源。
  • 在哪里可以找到 C 函数的源代码?我在 /usr/include/... 中看到了他们的标题。
  • 尝试搜索 glibc 或 ulibc 源代码。
  • memcpy() 在正常意义上不会成功或失败。它只是复制数据,如果它在源数组之外读取或在目标数组之外写入,可能会导致错误/异常,并且它也可能在这些数组之一之外读取或写入,而不会导致任何错误/异常并且只是默默地破坏数据.当我说“memcpy 做到这一点”时,我说的不仅仅是 C 标准库 memcpy 的实现,而是任何具有相同签名的函数——它没有足够的信息来做其他事情。

标签: c unix pointers memory-management


【解决方案1】:

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

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);
}

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

【讨论】:

  • @R - 第二个代码示例应该使用xy 而不是ab
  • 您可以通过添加char *z = b+n-1; 和测试x+i == y || x+i == z 来减少名义算术,因此不需要在每次迭代中第二次添加y+i
【解决方案2】:

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

如果源在目标之前(即源

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

【讨论】:

  • 另外一种情况是sourcedestination 指向不同的对象,在这种情况下source &lt; destination 会调用未定义的行为。大多数编译器都会使用(uintptr_t)source &lt; (uintptr_t)destination 来做你想做的事。
  • 确实,指针是 void *。我们可以像 OP 最初所做的那样计算差异,而不是进行比较。
  • 在原始示例中,数组srcdst 是两个不同的对象。由于是不同的对象,它们不能重叠,但这意味着原始示例中的 ptr1 - ptr2 是未定义的,您建议编写的 ptr1 &lt; ptr2 也是如此。见open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf 6.5.8:5, 6.5.6:9
  • 是的,标准没有定义 void 指针减法。不过,GCC 在迂腐模式下会发出一个简单的警告。
  • 不仅如此,“当两个指针相减时,都应该指向同一个数组对象的元素”。即使您的编译器没有警告,未定义的行为也是不好的。请参阅 blog.regehr.org/archives/213blog.llvm.org/2011/05/…(简短版本:您的编译器可能会利用程序中未定义的行为、未警告的未定义行为来做非常奇怪、意想不到的事情。不要使用未定义的行为)
【解决方案3】:

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

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

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

第三,在编写此类代码时,您必须非常小心指针运算(包括加法、减法和数组索引)的溢出情况。在val = abs(ptr1 - ptr2) 中,ptr1 - ptr2 可能是一个非常大的数字,并且它是无符号的,所以abs() 不会对其做任何事情,而int 是错误的类型来存储它。只是让你知道.

【讨论】:

  • “我不认为‘通过在复制操作开始前通知用户复制操作是否通过来增加 memcpy’是一个良好的概念。” - 这就是我们所说的bikeshedding,最好留下评论。重叠缓冲区和使用memcpy 在实践中会导致问题。例如,参见 LWN.net 的 On Breaking ThingsGlibc Change Exposing bugs;以及 Adob​​e 的 Flash 的讨论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-24
  • 2016-08-25
  • 2021-07-16
  • 2010-12-21
  • 2021-10-23
  • 2021-12-12
相关资源
最近更新 更多