在我的项目中,我们有一段代码:
// 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*
转换为int*
,这通常有更严格的对齐要求。但是技术主管说没关系,大多数编译器在转换后仍保持相同的指针值,我可以像这样编写代码。
所以这是我的问题:
reinterpret_cast
之间有什么区别吗?答案 0 :(得分:35)
1。在实际项目中进行转换后取消引用指针真的很安全吗?
如果指针碰巧没有正确对齐,它确实会导致问题。我亲眼看到并修复了将char*
转换为更严格对齐类型所导致的实际生产代码中的总线错误。即使您没有得到明显的错误,也可能会出现性能下降等不太明显的问题。即使您没有立即发现任何问题,严格遵守标准以避免UB是一个好主意。 (并且代码破坏的一条规则是严格的别名规则,§3.10/ 10 *)
如果缓冲区重叠(或者更好bit_cast<>()
),更好的选择是使用std::memcpy()
或std::memmove
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));
有些编译器比其他编译器更加努力,以确保char数组的排列比必要的更严格,因为程序员经常会这样做错误。
#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>(s.b); // unaligned in clang and msvc
}
S s;
void foo3() {
check_aligned<int>(s.b); // unaligned in clang, msvc, and gcc
}
int main() {
foo1();
foo2();
foo3();
}
2。 C风格的转换和reinterpret_cast之间有什么区别吗?
这取决于。 C风格的演员阵容根据所涉及的类型做不同的事情。指针类型之间的C风格转换将产生与reinterpret_cast相同的东西;参见§5.4显式类型转换(演员表示法)和§5.2.9-11。
3。 C和C ++之间有什么区别吗?
只要你处理C中合法的类型,就不应该这样做。
*另一个问题是C ++没有指定从一种指针类型转换为具有更严格对齐要求的类型的结果。这是为了支持甚至无法表示未对齐指针的平台。然而,今天典型的平台可以表示未对齐的指针,编译器将这种演员的结果指定为您期望的结果。因此,此问题是别名违规的次要问题。见[expr.reinterpret.cast] / 7.
答案 1 :(得分:27)
真的不行。对齐可能是错误的,并且代码可能违反严格的别名。你应该明确地解压缩它。
i1 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
等。这绝对是明确定义的行为,作为奖励,它也与字节顺序无关,与指针演员不同。
答案 2 :(得分:6)
在示例中,如果初始char指针正确对齐,那么您在此处显示的内容几乎对所有现代CPU都是安全的。一般来说,这是不安全的,不能保证有效。
如果初始char指针未正确对齐,这将在x86和x86_64上运行,但在其他体系结构上可能会失败。如果您很幸运,它只会让您崩溃并且您将修复您的代码。如果您运气不好,未对齐的访问权限将由操作系统中的陷阱处理程序修复,您将会遇到可怕的性能而没有任何关于它为何如此缓慢的明显反馈(我们正在谈论)对某些代码来说,这是一个很慢的问题,这对于20年前的alpha来说是一个巨大的问题。)
即使在x86&amp; co,未对齐访问将会更慢。
如果您想在今天和将来保持安全,只需memcpy
而不是像这样做任务。现代编译器可能会对memcpy
进行优化并做正确的事情,如果没有,memcpy
本身将具有对齐检测并且将做最快的事情。
另外,你的例子在一点上是错误的:sizeof(int)总是不是4。
答案 3 :(得分: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));
Casting违反了别名,这意味着编译器和优化器可以自由地将源对象视为未初始化。
关于你的3个问题:
reinterpret_cast
。答案 4 :(得分:1)
更新:
我忽略了这样一个事实:确实较小的类型可能相对于较大的类型未对齐,就像在你的例子中一样。您可以通过反转构建数组的方式来缓解该问题:将数组声明为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));
基元类型数组不会有任何问题。处理structured data(C中为struct
)的数组时会出现对齐问题,如果数组的原始初始类型大于它被转换为的数组,请参阅上面的更新。
将一个char数组转换为int数组应该是完全可以的,前提是用sizeof(int)
替换4的偏移量,以匹配代码应在其上运行的平台上的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));
请注意,只有当您以某种方式从一个平台以不同的字节顺序共享该数据时,才会遇到endianness个问题。否则,应该完全没问题。
答案 5 :(得分:1)
您可能希望向他展示根据编译器版本的不同之处:
除了对齐之外,还有第二个问题:标准允许您将int*
转换为char*
,而不是相反(除非char*
最初来自int*
{1}})。 See this post for more details.
答案 6 :(得分:0)
是否必须担心对齐取决于指针所源自的对象的对齐方式。
如果转换为具有更严格对齐要求的类型,则它不可移植。
char
数组的基数(在您的示例中)不需要比元素类型char
具有更严格的对齐方式。
但是,无论对齐方式如何,指向任何对象类型的指针都可以转换为char *
并返回。 char *
指针保留原始的更强对齐。
您可以使用union来创建更强烈对齐的char数组:
union u {
long dummy; /* not used */
char a[sizeof(long)];
};
联盟的所有成员都从同一地址开始:开头没有填充。当在存储中定义union对象时,它必须具有适合于最严格对齐的成员的对齐。
我们上面的union u
严格对齐long
类型的对象。
违反对齐限制可能会导致程序在移植到某些体系结构时崩溃。或者它可能有效,但是会产生轻微到严重的性能影响,具体取决于是否在硬件中实现了错位的内存访问(以一些额外的周期为代价)或者在软件中(内核陷阱,软件模拟访问,需要付费)许多周期)。