任何编译器都通过memcpy / memmove传输有效类型

时间:2015-12-01 01:34:53

标签: c language-lawyer memcpy strict-aliasing

根据N1570 6.5 / 6:

  

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

这表明即使在“long”和其他整数类型具有相同表示的系统上,以下内容也会调用Undefined Behavior:

#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指向的数据明显具有有效类型longp指向的对象没有声明类型,memcpy应设置有效类型存储到long。由于不允许读取使用类型为long_equiv的左值来读取有效类型为long的对象,因此代码将调用未定义的行为。

鉴于在C99之前memcpy是将一种类型的数据复制到另一种类型的存储的标准方法之一,关于memcpy的新规则会导致许多现有代码调用未定义的行为。如果规则是使用memcpy写入分配的存储而使目标没有任何有效类型,则将定义行为。

当用于将信息复制到分配的存储时,是否存在任何不表现为memcpy的编译器未设置目的地的有效类型,或者为了数据转换的目的应使用memcpy被认为是“安全的”?如果某些编译器确实将有效类型的源应用于目标,那么以类型无关的方式复制数据的正确方法是什么? “复制为字符类型数组”是什么意思?

2 个答案:

答案 0 :(得分:2)

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进行编译。

答案 1 :(得分:2)

在实验上,gcc 6.2的行为方式只有通过将memmove视为将源的有效类型转移到目的地才有道理。如果gcc可以确定源指针和目标指针匹配,则它将内存操作数视为仅通过其早期的有效类型可读,而不是作为最后使用字符类型写入的内存,因此可以使用任何类型进行访问。如果没有允许memcpy传输有效类型信息的规则,这种行为就是不合理的。

另一方面,gcc的行为有时在任何规则下都是不合理的,所以gcc的行为是否是其作者的结果并不一定清楚。对标准的解释,或者它是否被简单地破坏了。例如,如果它可以确定memcpy的目标目标包含与源相同的常量位模式,则它会将memcpy视为无操作,即使源持有的类型也是接下来用于读取目标存储,并且目标保持不同类型,编译器已决定不能为下一次读取设置别名。