编写符合严格别名的memcpy符合条件

时间:2014-07-31 13:57:47

标签: c++ c memcpy strict-aliasing

在询问“如何实现符合严格别名规则的memcpy函数”时的一般答案是

void *memcpy(void *dest, const void *src, size_t n)
{
    for (size_t i = 0; i < n; i++)
        ((char*)dest)[i] = ((const char*)src)[i];
    return dest;
}

但是,如果我理解正确,编译器可以自由地重新排序对memcpy的调用并访问dest,因为它可以使用任何其他指针类型的读取重新排序写入char *(严格别名规则阻止只重新排序来自char的读取*写入任何其他指针类型)。

这是否正确?如果是,是否有任何方法可以正确实现memcpy,还是应该依赖内置memcpy?

请注意,这个问题不仅涉及memcpy,还涉及任何反序列化/解码功能。

6 个答案:

答案 0 :(得分:6)

严格别名规则明确地将强制类型转换为char类型(请参阅下面的最后一个要点),因此编译器将在您的情况下执行正确的操作。将int转换为short等内容时,类型惩罚只是一个问题。在这里,编译器可能会做出会导致未定义行为的假设。

C99§6.5/ 7:

  

对象的存储值只能由具有以下类型之一的左值表达式访问:

     
      
  • 与对象的有效类型兼容的类型
  •   
  • 与对象的有效类型兼容的类型的合格版本
  •   
  • 与对象的有效类型对应的有符号或无符号类型
  •   
  • 与有效类型的对象的合格版本对应的有符号或无符号类型的类型,
  •   
  • 聚合或联合类型,其成员中包含上述类型之一(包括递归地,子聚合或包含联合的成员),或
  •   
  • 一个字符类型。
  •   

答案 1 :(得分:4)

由于(char*)dest(char const*)src都指向char,编译器必须假定它们可能是别名。另外,有一条规则说,指向字符类型的指针可以为任何内容添加别名。

所有这些都与memcpy无关,因为实际的签名是:

void* memcpy( void* restrict dest, void* restrict src, size_t n );

告诉编译器没有别名,因为用户保证它。您不能使用memcpy复制重叠区域而不会产生未定义的行为。

无论如何,给定的实现都没有问题。

答案 2 :(得分:1)

IANALL,但我不认为编译器会被允许以你描述的方式弄乱。通过非法指针类型呈现对对象的未定义访问,而不是通过在对象访问上指定另一个复杂的部分顺序,在规范中“实现”严格别名。

答案 3 :(得分:1)

这里似乎缺少的是严格别名(6.5 / 7)取决于术语有效类型(6.5 / 6)。有效类型对函数memcpy(6.5 / 6)有明确的特殊规则:

  

如果将值复制到没有声明类型的对象中   memcpymemmove,或者被复制为字符类型数组,然后该访问的修改对象的有效类型以及不修改该值的后续访问是对象的有效类型从中复制值,如果有的话。

因此,我认为在memcpy函数中提到严格的别名甚至没有意义。如果您知道有效类型,则只能说严格别名。现在,您如何根据以上内容确定? memcpy的内部是memcpy的副本吗?

这就像是说“为了理解memcpy中使用哪种有效类型,你必须先了解memcpy中使用的哪种有效类型”。

所以我不太清楚这个问题或所发布的任何答案是否有任何意义。

答案 4 :(得分:0)

是的,你错过了什么。编译器可能会将写入重新排序到dest并读取到dest。现在,由于src的读取发生在写入dest之前,而您dest的假设读取发生在写入dest之后,因此从dest读取{1}}从src读取后发生。

答案 5 :(得分:0)

如果一个对象没有声明的类型,它可能获得的任何有效类型只有在下次修改对象时才有效。使用字符类型的指针写入对象计为修改它,从而取消设置旧类型,但是通过字符类型指针写入它不会设置新类型,除非此类操作作为“作为字符数组的数组复制”的一部分发生“, 不管它是什么意思。没有有效类型的对象可以合法地阅读任何类型。

由于“作为字符类型数组复制”的有效类型语义与memcpy的有效类型语义相同,因此可以使用字符指针来编写memcpy实现以进行读写。它可能不会以memcpy允许的方式设置目标的有效类型,但如果目标没有有效类型,则使用memcpy时定义的任何行为都将被相同地定义[正如恕我直言memcpy}的情况一样。

我不确定是谁提出这样的想法:编译器可以假设已经获得有效类型的存储在使用char*进行修改时保持该有效类型,但标准中没有任何内容证明它是正确的。如果您需要使用代码来使用gcc,请指定它必须与-fno-strict-aliasing标志一起使用,除非或直到gcc开始遵守标准。没有理由向后试图支持编译器,编译器的作者不断寻找新的情况以忽略别名,即使标准要求他们识别它也是如此。