memcpy.c 应该如何对内存重叠做出反应?

How is memcpy.c supposed to react to memory overlap?

提问人:marinsucks 提问时间:10/19/2023 最后编辑:chqrliemarinsucks 更新时间:10/22/2023 访问量:132

问:

我正在尝试重现 的行为。但是,当我尝试使用重叠内存测试时,我收到的不是跟踪陷阱,而是不同的结果。例如,使用以下函数,结果为 。memcpymainOveOveOveOveO�

#include <stdio.h>
#include <string.h>

int main() {

    char buffer[] = "Overlap Test";
    ft_memcpy(buffer + 3, buffer, strlen(buffer) + 1);
    printf("%s\n", buffer + 3);

    return 0;
}

我确实将关键字放在函数声明中,我不明白为什么编译器在这种情况下不将其作为跟踪陷阱。当然,chatgpt对此也找不到解释是没有用的。在内存重叠的情况下,会出现这样的结果吗?restrict

void    *ft_memcpy(void *restrict dst, const void *restrict src, size_t n)
{
    char    *ptr;
    char    *d;
    char    *s;

    d = dst;
    s = (char *)src;
    ptr = dst;
    if (src == NULL || n == 0)
        return (NULL);
    while (n > 0)
    {
        *d = *s;
        d++;
        s++;
        n--;
    }
    return (ptr);
}
c 内存 重叠

评论

0赞 Refugnic Eternium 10/19/2023
我相信 glibc 资源是免费提供的。为什么不看看 GCC 的维护者是如何做到的呢?
0赞 marinsucks 10/19/2023
我确实尝试过查看这些,但我很难理解苹果的那个,而且我在 geeksforgeeks.org/write-memcpy 上找到的更容易访问的那个似乎与我的没有太大区别。
6赞 Dipstick 10/19/2023
memcpy()对于重叠区域未定义,因此可以针对非重叠情况进行优化。 需要处理重叠区域。memmove()
0赞 0___________ 10/19/2023
@marinsucks因为 geeksforgeeks one 和你一样天真,它在那里说明了问题。真正的写法要优化得多。示例: : github.com/lattera/glibc/blob/master/string/memcpy.c
0赞 chux - Reinstate Monica 10/19/2023
@marinsucks,撇开,没有理由.用。castconst char *s = src;

答:

2赞 0___________ 10/19/2023 #1

我正在尝试重现 memcpy 的行为。但是,当我尝试时 通过重叠的内存测试,我收到的不是跟踪陷阱 不同的结果。

在这种情况下(根据 C 标准),结果为 undefined。memcpy

在内存重叠的情况下,会出现这样的结果吗?

不,因为它是 UNDEFINED,你不应该期待任何事情。

我确实在函数声明中放置了限制关键字,并且我 不明白为什么编译器不将其作为跟踪陷阱放入 这种情况。

因为不是这样工作的。它不添加任何检查。承诺你的函数将以某种方式运行,允许编译器进行更积极的优化。restrict


胡说八道的UB审议:

此外,许多 x86 glibc 实现不使用单字节复制,因此您的(朴素)函数将无法重现它们的行为。memcpy

评论

0赞 marinsucks 10/19/2023
所以你的意思是我应该直接在函数中实现重叠检查?还是以其他方式从头开始重做函数?
0赞 0___________ 10/19/2023
@marinsucks重叠检查在 C 语言中非常复杂。基本上,您无法以可移植的方式有效地实现它们。在这里看到我的问题 stackoverflow.com/questions/74946095/...
0赞 Deduplicator 10/19/2023
“限制”限制调用方,而不是被调用方。这句话被颠倒了。
0赞 Gerhardh 10/19/2023
@marinsucks为什么要浪费时间在未定义的函数中进行此类检查呢?这就是打算使用的用途。如果你也想实现它,你可以在那里添加重叠检查,但为什么要用额外的检查来污染功能呢?memmovmemcpy
0赞 0___________ 10/19/2023
可移植代码中的@Gerhardh重叠检查非常昂贵
2赞 chux - Reinstate Monica 10/19/2023 #2

我正在尝试重现 memcpy 的行为。

Bug:错误的返回值

当 时,返回 。OP 的代码也应该执行相同的操作。n == 0memcpy()dest

// if (src == NULL || n == 0)
//        return (NULL);
if (src == NULL) // memcpy(..., NULL, ...) is not defined.  Do whatever you want.
  return NULL;  // or maybe `n = 0;`
if (n == 0) 
  return dest;

Bug:空间不足

对于,是一个问题,因为没有足够的空间容纳字符。@chqrliechar buffer[] = "Overlap Test";ft_memcpy(buffer + 3, buffer, strlen(buffer) + 1);buffer + 3strlen(buffer) + 1


memcpy.c 应该如何对内存重叠做出反应?
在内存重叠的情况下,会出现这样的结果吗?

给定函数签名中的 2,使用重叠缓冲区是未定义行为 (UB)。没有预期的结果。restrict

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

即使 OP 的功能与今天的 OP 编译器一样,也不确定它明天或其他机器会运行。它是UB。ft_memcpy()memcpy()

我建议不要使用重叠缓冲区测试等效功能 - 因为它是 UB。
请注意,当 .
n == 0


相反,请考虑“memcpy.c 应该如何对内存重叠做出反应?memmove()

这更棘手。

复制的发生方式是,首先将 所指向的对象中的字符复制到与 和所指向的对象不重叠的临时字符数组中,然后将临时数组中的字符复制到 所指向的对象中。
C23dr § 7.26.2.3 2
ns2ns1s2ns1

OP 的代码需要更改才能满足这一点。要做得好是一个挑战,因为通常的方法是比较地址。根据哪个更大,从源缓冲区的开头或结尾复制。然而,对于用户代码,没有定义的方式来比较任意地址的顺序。各种通常有效的技巧比比皆是。

下面的没有 UB,但可能无法像在所有机器上一样编译或运行。memmove()

#include <stdlib.h>
#include <stdint.h>

void *ft_memmove(void *dst, const void *src, size_t n) {
  // Not needed as passing `NULL` for either pointer parameter is UB in memmove().
  if (dst == NULL || src == NULL) {
    n = 0;
  }
  // Weakness: uintptr_t is an optional type.
  uintptr_t d = (uintptr_t) dst;
  uintptr_t s = (uintptr_t) src;
  // Weakness: `d < s` is not a defined order compare of the pointers.
  if (d < s) {
    for (size_t i = 0; i < n; i++) {
      ((char*)dst)[i] = ((const char*)src)[i];
    }
  } else {
    while (n > 0) {
      n--;
      ((char*)dst)[n] = ((const char*)src)[n];
    }
  }
  return dst;
}

评论

0赞 chqrlie 10/21/2023
你会考虑吗?while (n --> 0)
0赞 chux - Reinstate Monica 10/21/2023
@chqrlie啊,是的,箭头操作员。