任何编译器都通过memcpy / memmove传输有效类型
根据N1570 6.5 / 6:
如果使用memcpy或memmove将值复制到没有声明类型的对象中,或者将其复制为字符类型数组,则该访问的修改对象的有效类型以及不修改该值的后续访问的有效类型是复制值的对象的有效类型(如果有)。
这表明即使在“long”和其他整数类型具有相同表示的系统上,以下内容也会调用未定义的行为:
#if ~0UL == ~0U #define long_equiv int #elif ~0UL == ~0ULL #define long_equiv long long #else #error Oops #endif long blah(void) { long l; long_equiv l2; long_equiv *p = malloc(sizeof (long)); l = 1234; memcpy(p, &l, sizeof (long)); l2 = *p; free(p); // Added to address complaint about leak return l2; }
由于l
指向的数据明显具有有效类型long
并且p
指向的对象没有声明类型,因此memcpy
应将存储的有效类型设置为long
。 由于long_equiv
读取使用long_equiv
类型的左值来读取有效类型为long
的对象,因此代码将调用Undefined Behavior。
鉴于在C99之前, memcpy
是将一种类型的数据复制到另一种类型的存储的标准方法之一,关于memcpy
的新规则导致许多现有代码调用未定义的行为。 如果规则是使用memcpy
写入已分配的存储而离开目标而没有任何有效类型,则将定义该行为。
当用于将信息复制到分配的存储时,是否有任何编译器不表现为memcpy
留下目的地的有效类型,或者为了数据转换而使用memcpy
被认为是“安全的”? 如果某些编译器确实将有效类型的源应用到目标,那么以类型无关的方式复制数据的正确方法是什么? “复制为字符类型数组”是什么意思?
C标准说有效类型是转移的。 因此,根据定义,所有符合要求的编译器都会转移有效类型。
您的代码示例通过违反严格别名规则导致未定义的行为,因为有效类型long
的值由long long
类型的左值读取。
在C89中也是如此,我不确定你所说的“C99中的新规则”(除了long long
不在C89中的事实)。
确实,当C标准化时,一些现有代码具有未定义的行为。 人们继续编写具有未定义行为的代码也是事实。
“复制为字符类型数组”是什么意思?
这意味着使用字符类型逐个字符地复制。
什么是以类型无关的方式复制数据的正确方法?
据我所知,不可能“擦除有效类型”。 要使用long long *
正确读取值,您必须指向long long
有效类型的位置。
在您的代码中,例如:
// If we have confirmed that long and long long have the same size and representation long long x; memcpy(&x, p, sizeof x); return x;
联盟别名是另一种选择。
如果你不喜欢这一切,那么使用-fno-strict-aliasing
进行编译。
在实验上,gcc 6.2的行为方式只有将memmove
视为将源的有效类型传递到目的地才有道理。 如果gcc可以确定源指针和目标指针匹配,则它将内存操作数视为仅通过其早期的有效类型可读,而不是作为最后使用字符类型写入的内存,因此可以使用任何类型进行访问。 如果没有允许memcpy
传输有效类型信息的规则,这种行为将是不合理的。
另一方面,gcc的行为有时在任何规则下都是不合理的,因此gcc的行为是否是其作者对标准的解释的结果,或者它是否被简单地破坏并不一定清楚。 例如,如果它可以确定memcpy
的目标目标包含与源相同的常量位模式,它将把memcpy
视为无操作,即使源持有下次用于读取目标存储的类型,并且目标保持不同类型,编译器已决定不能为下一次读取别名。