我应该担心指针投射期间的alignment?

在我的项目中,我们有这样一段代码:

// raw data consists of 4 ints unsigned char data[16]; int i1, i2, i3, i4; i1 = *((int*)data); i2 = *((int*)(data + 4)); i3 = *((int*)(data + 8)); i4 = *((int*)(data + 12)); 

我跟我的技术负责人谈到,这个代码可能不是可移植的,因为它试图将一个unsigned char*成一个通常具有更严格alignment要求的int* 。 但是技术领导说没关系,大多数编译器在投射后仍然是相同的指针值,我可以像这样写代码。

坦率地说,我并不确信。 经过研究,我发现有些人不喜欢使用像上面这样的指针铸件,例如在这里和这里 。

所以这是我的问题:

  1. 在一个真实的项目中投射后,是否真正安全地取消引用指针?
  2. C风格的铸造和reinterpret_cast有什么区别吗?
  3. C和C ++有什么区别?

在一个真实的项目中投下指针后解除引用是否真的很安全?

如果指针碰巧没有被正确alignment,那真的会导致问题。 我亲自看到并修复了真实的总线错误,通过将char*转换为更严格的alignmenttypes而导致的生产代码。 即使你没有明显的错误,你也可以有一些不太明显的问题,比如性能下降。 严格遵循标准避免UB是一个好主意,即使你没有立即看到任何问题。 (代码破坏的一个规则是严格的别名规则,§3.10 / 10 *)

一个更好的select是使用std::memcpy()std::memmove如果缓冲区重叠(或更好,但bit_cast<>()

 unsigned char data[16]; int i1, i2, i3, i4; std::memcpy(&i1, data , sizeof(int)); std::memcpy(&i2, data + 4, sizeof(int)); std::memcpy(&i3, data + 8, sizeof(int)); std::memcpy(&i4, data + 12, sizeof(int)); 

有些编译器比其他编译器更努力工作,以确保字符数组的排列更加严格,因为程序员经常会犯这个错误。

 #include <cstdint> #include <typeinfo> #include <iostream> template<typename T> void check_aligned(void *p) { std::cout << p << " is " << (0==(reinterpret_cast<std::intptr_t>(p) % alignof(T))?"":"NOT ") << "aligned for the type " << typeid(T).name() << '\n'; } void foo1() { char a; char b[sizeof (int)]; check_aligned<int>(b); // unaligned in clang } struct S { char a; char b[sizeof(int)]; }; void foo2() { S s; check_aligned<int>(sb); // unaligned in clang and msvc } S s; void foo3() { check_aligned<int>(sb); // unaligned in clang, msvc, and gcc } int main() { foo1(); foo2(); foo3(); } 

http://ideone.com/FFWCjf

2. C风格的cast和reinterpret_cast有什么区别吗?

这取决于。 C风格的演员根据所涉及的types做不同的事情。 指针types之间的C风格转换将导致与reinterpret_cast相同的事情; 见§5.4 显式types转换(转换符号)和§5.2.9-11。

3. C和C ++有什么区别吗?

不应该只要你处理C中合法的types


*另一个问题是C ++没有指定从一个指针types转换为具有更严格alignment要求的types的结果。 这是为了支持甚至无法表示未alignment指针的平台。 然而,今天的典型平台可以代表未alignment的指针,编译器指定这样的转换的结果是你期望的。 因此,这个问题是次要的违反别名。 见[expr.reinterpret.cast] / 7。

这不太好,真的。 alignment可能是错误的,代码可能会违反严格的别名。 你应该明确地解开它。

 i1 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24; 

等等。这绝对是明确定义的行为,作为奖励,它也是字节sorting无关的,不像你的指针。

在这个例子中,如果初始字符指针正确alignment,那么在这里显示的内容对于几乎所有的现代CPU都是安全的。 一般来说这是不安全的,不能保证工作。

如果初始字符指针未正确alignment,则可在x86和x86_64上运行,但在其他体系结构上可能会失败。 如果你幸运的话,它只会让你崩溃,你会修复你的代码。 如果你运气不好,那么你的操作系统中的陷阱处理程序将会解决未alignment的访问问题,而且在没有任何明显的反馈意见的情况下,性能会非常糟糕(对于某些代码,这是20年前阿尔法的一个大问题)。

即使在x86&co上,未alignment的访问也会变慢。

如果你想今天和将来安全,只需要memcpy而不是像这样做。 现代编译器可能会对memcpy进行优化,并做正确的事情,如果没有, memcpy本身将有alignment检测,并将做最快的事情。

另外,你的例子在一点上是错误的:sizeof(int)并不总是4。

解压char缓冲数据的正确方法是使用memcpy

 unsigned char data[4 * sizeof(int)]; int i1, i2, i3, i4; memcpy(&i1, data, sizeof(int)); memcpy(&i2, data + sizeof(int), sizeof(int)); memcpy(&i3, data + 2 * sizeof(int), sizeof(int)); memcpy(&i4, data + 3 * sizeof(int), sizeof(int)); 

投射违反别名,这意味着编译器和优化器可以自由地将源对象视为未初始化。

关于你的3个问题:

  1. 不,取消引用转换指针通常是不安全的,因为别名和alignment。
  2. 不,在C ++中,C风格的转换是根据reinterpret_cast定义的。
  3. 不,C和C ++同意基于模型的别名。 基于联合的别名的处理方式有所不同(C允许在某些情况下; C ++不允许)。

更新:我忽略了这样一个事实,即更小的types相对于更大的types可能是不alignment的,就像在你的例子中那样。 你可以通过反转你的数组的方式来解决这个问题:将你的数组声明为一个int数组,并且当你需要以这种方式访问​​它时将它转换为char *

 // raw data consists of 4 ints int data[4]; // here's the char * to the original data char *cdata = (char *)data; // now we can recast it safely to int * i1 = *((int*)cdata); i2 = *((int*)(cdata + sizeof(int))); i3 = *((int*)(cdata + sizeof(int) * 2)); i4 = *((int*)(cdata + sizeof(int) * 3)); 

原始types的数组不会有任何问题。 当处理结构化数据数组(C语言中的结构 struct )时, 如果数组的原始types大于它所转换的数组,则会发生alignment问题,请参阅上面的更新。

如果用sizeof(int)replace4的偏移量,以匹配代码应该在其上运行的平台上的int大小,那么将char数组强制转换为int数组应该是完全可以的。

 // raw data consists of 4 ints unsigned char data[4 * sizeof(int)]; int i1, i2, i3, i4; i1 = *((int*)data); i2 = *((int*)(data + sizeof(int))); i3 = *((int*)(data + sizeof(int) * 2)); i4 = *((int*)(data + sizeof(int) * 3)); 

请注意,只有当您以不同的字节顺序将数据以某种方式从一个平台共享到另一个平台时,才会遇到字节码问题。 否则,应该是完美的。

您可能想向他展示如何根据编译器版本的不同而有所不同:

  • GCC 4.3.4
  • GCC 4.7.2

除了alignment之外还有第二个问题:标准允许你将int*char*而不是反过来(除非char*最初是从int* )。 看到这个职位了解更多详情。

无论您是否担心alignment取决于指针始发对象的alignment方式。

如果您投射到具有更严格alignment要求的types,则不能移动。

在你的例子中,一个char数组的基数不需要比元素typeschar有更严格的alignment。

但是,指向任何对象types的指针都可以转换为char *并返回,而不pipealignment。 char *指针保留原始的更强的alignment。

你可以使用union来创build一个更加强alignment的char数组:

 union u { long dummy; /* not used */ char a[sizeof(long)]; }; 

工会的所有成员都从同一地址开始:开始时没有填充。 当一个联合对象被定义在存储中时,它必须有一个适合于最严格alignment的成员的alignment方式。

我们上面的union u对于longtypes的对象是严格alignment的。

违反alignment限制可能会导致程序移植到某些体系结构时崩溃。 或者它可能工作,但对性能有轻微或严重的影响,这取决于是否在硬件中实现了未alignment的内存访问(以一些额外的周期为代价),或者是软件(陷入内核的软件模拟访问的内核,许多周期)。