在标准C中从头实现memcpy在技术上是不可能的吗?

时间:2019-01-23 22:42:10

标签: c char language-lawyer memcpy

朱(Howard Chu)writes

  

在最新的C规范中,不可能编写malloc或memcpy的“合法”实现。

这是对的吗?我的印象是,在过去,该标准(至少)的意图是可以实现以下目的:

void * memcpy(void * restrict destination, const void * restrict source, size_t nbytes)
{
    size_t i;
    unsigned char *dst = (unsigned char *) destination;
    const unsigned char *src = (const unsigned char *) source;

    for (i = 0; i < nbytes; i++)
        dst[i] = src[i];
    return destination;
}

这里违反了最新C标准的哪些规则?还是memcpy的规范的哪一部分没有被该代码正确实现?

2 个答案:

答案 0 :(得分:1)

对于malloc函数,第6.5§6段清楚地表明,不可能编写一致且可移植的C实现:

  

访问其存储值的对象的有效类型是已声明的对象类型。   对象,如果有的话(87) ...

(非规范性)注释87说:

  

已分配的对象没有声明的类型。

声明没有声明类型的对象的唯一方法是...通过分配函数返回该对象!因此,在分配函数内部,您必须具有某物,这是标准所不允许的,以设置没有声明类型的内存区域。

在常见的实现中,标准库malloc和free实际上是在C中实现的,但是系统对此有所了解,并假设malloc中提供的字符数组没有声明的类型。句号。

但是同一段的其余部分说明,编写memcpy实现并没有真正的问题(强调我的想法):

  

...如果通过一个值将值存储到没有声明类型的对象中   具有非字符类型的左值,则左值的类型变为   该访问和不修改的后续访问的对象的有效类型   储值。如果将值复制到没有声明类型的对象中,则使用   memcpy或memmove或复制为字符类型数组,然后是有效类型   修改后的对象的访问权限以及不修改   value是从中复制值的对象的有效类型(如果有)。对于   对没有声明类型的对象的所有其他访问,则该对象的有效类型为   只是用于访问的左值的类型。

假设您将对象复制为字符类型的数组,这是严格的别名规则所允许的特殊访问,实现memcpy不会出现问题,并且您的代码是可能且有效的实现。 / p>

恕我直言,霍华德·朱(Howard Chu)的咆哮与旧商品memcpy的用法有关,该用法不再有效(假设sizeof(float) == sizeof(int)):

float f = 1.0;
int i;
memcpy(&i, &f, sizeof(int));         // valid: copy at byte level, but the value of i is undefined
print("Repr of %f is %x\n", i, i);   // UB: i cannot be accessed as a float

答案 1 :(得分:0)

TL; DR
只要memcpy基于幼稚的逐个字符复制,就可以了。

尚未进行优化,无法移动可在单个指令中复制的最大对齐类型大小的块。后者是标准lib实现的方式。


所涉及的是这种情况:

void* my_int = malloc(sizeof *my_int);
int another_int = 1;

my_memcpy(my_int, &another_int, sizeof(int));

printf("%d", *(int*)my_int); // well-defined or strict aliasing violation?

说明:

  • 指向我的my_int的数据没有有效的类型。
  • 当我们将数据复制到my_int位置时,可能会担心由于unsigned char使用的是强制有效类型变为my_memcpy
  • 然后,当我们通过int*读取该内存位置时。我们会违反严格的别名吗?

但是,这里的键是C17 6.5 / 6中指定的有效类型规则中的一个特殊例外,重点是我的:

  

如果将值复制到没有声明类型的对象,请使用   memcpymemmove或复制为字符类型数组然后修改对象的有效类型访问和后续访问,这些访问不会修改   值是从中复制值的对象的有效类型(如果有)。

由于我们确实将数组复制为字符类型,因此my_int指向的有效类型将成为复制值的对象another_int的有效类型。

所以一切都很好。

此外,您restrict对参数进行了限定,因此就不必像真实的memcpy那样两个指针是否可能互为别名。

值得注意的是,在C99,C11和C17中,此规则保持不变。有人可能会说这是编译器供应商滥用的非常糟糕的规则,但这是另一回事。