我应该担心指针投射过程中的对齐吗?

Eri*_*c Z 51 c c++ casting 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));
Run Code Online (Sandbox Code Playgroud)

我和我的技术负责人谈到这个代码可能不便携,因为它试图将a unsigned char*转换为int*通常具有更严格的对齐要求的代码.但是技术主管说没关系,大多数编译器在转换后仍保持相同的指针值,我可以像这样编写代码.

坦率地说,我并不是真的相信.经过研究,我发现有些人反对使用上面的指针铸件,例如,这里这里.

所以这是我的问题:

  1. 在真实项目中投射后取消引用指针真的很安全吗?
  2. C型铸造和reinterpret_cast?之间有什么区别吗?
  3. C和C++之间有什么区别吗?

bam*_*s53 36

1.在真实项目中投射后取消引用指针真的很安全吗?

如果指针碰巧没有正确对齐,它确实会导致问题.我个人已经看到并修复了真实的生产代码中的总线错误,这些错误是由char*一个更严格的对齐类型引起的.即使您没有得到明显的错误,也可能会出现性能下降等不太明显的问题.即使您没有立即发现任何问题,严格遵守标准以避免UB是一个好主意.(并且代码破坏的一条规则是严格的别名规则,§3.10/ 10*)

更好的选择是使用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));
Run Code Online (Sandbox Code Playgroud)

有些编译器比其他编译器工作更努力,以确保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();
}
Run Code Online (Sandbox Code Playgroud)

http://ideone.com/FFWCjf

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

这取决于.C风格的演员表根据所涉及的类型做不同的事情.指针类型之间的C风格转换将产生与reinterpret_cast相同的东西; 参见§5.4 显式类型转换(强制转换符号)和§5.2.9-11.

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

只要您处理C中合法的类型,就不应该存在.


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

  • @StackedCrooked让你想知道是否有人决定修补编译器以处理他的遗留代码比修复对齐问题更容易,当他尝试将其移植到新平台时... (4认同)

Pup*_*ppy 28

真的,这不太好.对齐可能是错误的,并且代码可能违反严格的别名.你应该明确解压缩它.

i1 = data[0] | data[1] << 8 | data[2] << 16 | data[3] << 24;
Run Code Online (Sandbox Code Playgroud)

这绝对是明确定义的行为,作为奖励,它也与字节顺序无关,与指针转换不同.

  • 这是否是字节序独立取决于字节来自何处. (8认同)
  • 这个字节顺序如何独立?我会说这假设数据存储小端整数. (3认同)
  • ...并且它不违反严格的别名。+1 (2认同)
  • `std :: uint32_t`会更好 (2认同)

Art*_*Art 7

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

如果初始char指针未正确对齐,则这将在x86和x86_64上运行,但在其他体系结构上可能会失败.如果你很幸运,它只会让你崩溃,你将修复你的代码.如果你运气不好,你的操作系统中的陷阱处理程序会修复未对齐的访问权限,你会有很糟糕的表现而没有任何关于为什么它如此缓慢的明显反馈(我们说某些代码的速度很慢,这是20年前阿尔法的一个巨大问题).

即使在x86&co上,未对齐访问也会变慢.

如果你想在今天和将来保持安全,memcpy而不是像这样做任务.现代编译器可能会优化memcpy并做正确的事情,如果没有,memcpy它本身将具有对齐检测并将做最快的事情.

此外,您的示例在某一点上是错误的:sizeof(int)并不总是4.


eca*_*mur 5

解压缩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));
Run Code Online (Sandbox Code Playgroud)

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

关于您的3个问题:

  1. 不,由于别名和对齐方式,取消引用强制转换指针通常是不安全的。
  2. 不,在C ++中,C样式转换是根据定义的reinterpret_cast
  3. 不,C和C ++在基于类型转换的别名上达成共识。基于联合的别名的处理方式有所不同(在某些情况下C允许使用; C ++不允许)。

  • “编译器和优化器可以自由地将源对象视为未初始化的对象”:编译器还可以自由地假定源对象为橙色。或“ 2 + 2 ==“猫王”“为真。 (2认同)