首页 文章

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

提问于
浏览
8

在询问“如何实现符合严格别名规则的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

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

    C99§6.5/ 7:

    对象的存储值只能由具有以下类型之一的左值表达式访问:与对象的有效类型兼容的类型,与对象的有效类型兼容的类型的限定版本,类型对应于对象的有效类型的有符号或无符号类型,对应于对象有效类型的限定版本的有符号或无符号类型,包含上述类型之一的聚合或联合类型其成员(包括递归地,子聚合或包含联合的成员)或字符类型 .

  • 1

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

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

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

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

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

  • 5

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

  • 0

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

    如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型为复制值的对象的有效类型(如果有) .

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

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

    所以我不太明白这个问题或任何答案是否有意义 .

  • 1

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

  • 4

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

    由于"copying as an array of character type"的有效类型语义与 memcpy 的有效类型语义相同,因此可以使用字符指针来编写memcpy实现以进行读写 . 它可能不会以允许的方式设置目标的有效类型,但是如果目标没有有效类型,则使用 memcpy 时定义的任何行为将被相同地定义[因为恕我直言应该是这样的 memcpy ] .

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

相关问题