C ++中施放的普通指针会导致不确定的行为,从而导致崩溃或错误的输出。严格的混叠是一项规则,其次是最不知情的侵犯的编译器,导致了不可预测的行为。如果您曾经使用过Reinterpret_cast或类型铸造,那么您可能会很脆弱!通过允许安全的替代方案,例如 std :: memcpy,,,, std :: bit_cast,,,, 未签名的char*, 和 工会 存在。本文解释了什么是严格的混叠,为什么危险以及如何安全有效地编码。
目录:
C ++的严格混叠是什么?
严格的别名规则是C ++(和C)中的优化规则,该规则允许编译器假设不同类型的指针没有指向相同的内存。这可以由编译器进行积极的代码优化。如果您违反了,它可能会导致 “不确定的行为”, 而且您的程序可能会崩溃并给您错误的结果。
在C ++中严格混杂的工作
在C ++中, 严格的别名 指定指向不同类型的指针被假定为没有别名,即它们没有指向相同的内存位置。优化代码变得容易,但可能会导致 不确定的行为。
例如,如果我们将42存储在一个 int* 然后将其存储在3.14F中 漂浮* 指向同一位置,我们可能 违反编译器对内存访问的期望 因此,编译器不会重新加载 int 在打印时值,假设浮点写入没有影响它。这甚至可能导致输出不正确或崩溃。如果有必要将指针转换为不兼容类型的指针,请使用 std :: memcpy 或者 std :: bit_cast (因为C ++ 20)而不是在不兼容类型之间铸造以避免破坏严格的混叠。
让我们检查一个程序,看看严格的别名如何有效
例子:
输出:

在此示例中 int* 和 漂浮* 重叠,但是编译器没有信息可以知道它们重叠,这导致它优化了 *IP = 42;线路可能会产生不良结果。要安全地访问跨类型的内存,请使用 std :: memcpy 或者 std :: bit_cast (C ++ 20)。作为副作用,这有助于防止不确定的行为以及提高性能。
编译器如何优化严格的混溶
编译器假设不同类型的类型永远不会别名,除非您明确允许它。这意味着:
- 它基于类型信息来重新定位加载/存储。
- 它消除了不必要的内存读取,因为它假定内存没有更改。
- 它将值保持在寄存器中,而不是从内存中再次加载它们。
安全的替代方案,以避免严格的别名C ++
有一些安全的替代方法可以避免使用严格的别名 std :: memcpy,std :: bit_cast, 和 未签名的char*。
1。使用std :: memcpy避免严格的别名
std :: memcpy 是一种使用不同类型的相同内存的安全方法,而不会破坏严格的别名。使用memcpy,您是通过字节复制内存的,允许编译器正确处理它,而不是直接施放指针(这导致不确定的行为)。这也确保在原始数据仍然存在时不会进行错误的优化。
例子:
输出:

在上面的代码中,我们使用 std :: memcpy 避免直接铸造 漂浮* 到一个 int* 复制原始内存 漂浮* 到一个 int。 为了避免不确定的行为,这要确保
2。使用std :: bit_cast避免严格的别名
std :: bit_cast (在C ++ 20中添加)允许重新解释内存,而不会破坏严格的混叠规则。它本身将源数据将源数据逐个地复制到新类型中,从而阻止编译器优化它。与指针铸造(reinterpret_cast)不同, std :: bit_cast 不会创建不确定的行为,因为它保留了原始数据的完整位模式。
例子:
笔记: 以上代码不支持普通编译器,它仅在C ++ 20编译器中起作用。
在上述代码中, std :: bit_cast(f) 安全地重新诠释 漂浮 作为 int 而不会违反严格的别名。此功能可确保转换在二进制级别上完成,并且不会被编译器优化,这可能导致不确定的行为。
3。使用未签名的char*避免严格的别名
在C ++中,严格的混叠规则,没有违反严格的混叠规则, 未签名的char* 可以访问任何对象的原始内存,这也使二进制数据操纵,序列化和调试变得容易。
例子:
输出:

在上述代码中,我们声明 结构数据 与 int 和 漂浮并将无符号字符*分配给构造数据,以读取其原始内存为字节。在这里,我们在十六进制中打印每个字节,以确保如何存储数据。这种方法可用于调试,序列化和分析低级内存。
4。使用联合类型的双关
在C ++中,这是访问与不同类型相同内存的另一种安全方法,而不会引起 不确定的行为 。在联合类型的双关词中,一个不同的成员 联盟 共享相同的内存,而不会违反严格的别名规则。
例子:
输出:

工会可以通过允许使用类型的双张力来为其他类型的内存共享,而不会违反严格的混叠。推杆 3.14f 在 DF 并将其读出来 di 产生意想不到的整数值,因为它读取了浮子的二进制形式。
例外摘要
例外 | 安全的? | 原因 |
char*,unsigned char*,std :: byte* | 是的 | 可以安全别名 |
联合类型双关 | 或许 | 在某些编译器中工作,但不能保证便携式 |
std :: memcpy | 是的 | 复制数据而不是混音 |
std :: bit_cast(C ++ 20) | 是的 | 安全类型重新解释 |
检测严格的混叠违规行为
严格的别名违规行为可能是默默的 不确定的行为而不是编译器错误,标志可以帮助定位潜在的故障点。在GCC和Clang中,-fstrict -oliasing可以实现严格的混叠优化,并且 – Wtrict -Oliasing发出有关违反严格异化规则的代码的警告。
如果您面临混叠问题,则运行以下代码以及这些标志可以捕获您:
例子:
输出:

1078523331是读取3.14F的二进制表示的结果,该表示为int。这就是为什么行为与我们期望的相反的原因,因为reinterpret_cast不会修改对象的位表示,而对象也不可恶,因此编译器根据完全不同的规则来优化,破坏所有严格的别名规则。在这里,这取决于浮点值3.14F如何存储在内存中,然后从内存中以int的形式获取。
检测违规行为的方法
使用GCC或Clang使用:
g++ -std=c++17 -O2 -Wall -Wstrict-aliasing test.cpp
编译器诊断在编译时严格违反违规行为,发出警告或导致运行时不确定的行为。使用std :: memcpy或std :: bit_cast(C ++ 20),以避免此问题。
结论
严格的混叠是一个很棒的C ++优化规则,因为它会导致有效的内存访问,但如果违反规则,也会导致不确定的行为。使用不同类型的类型不适当地违反严格的混溶,可以生成任何形式的编译器优化(但这是一个边缘案例,因为它被引用了可移植性问题),但是可以使用一些成年人来保证安全的类型,例如STD :: memcpy,memcpy,std :: bit_bit_cast :: bit_cast(c+++carteccast cartect cartect(c++carlignect),以实现carlias carrip for varrias for varia varias way way way varias。另一方面,用编译器标志(例如 – wstrict -oliasing)标记违规行为可以帮助调试问题。由于编程模型的正确性越正确,因此,严格的混叠是使用最佳实践的低级编程,嵌入式系统和关键性能程序的关键。
关于C ++严格的混叠规则的常见问题
1。定义C ++中的严格混叠
严格的别名规则是C ++(和C)中的优化规则,该规则允许编译器假设不同类型的指针没有指向相同的内存。如果您违反了,它可能会导致 “未定义的行为”。
2。为什么破坏严格的混叠会导致不确定的行为?
当不同的指针类型类型时,这些优化可能导致结果不正确或崩溃。
3。如何避免严格的违规违规?
使用安全替代方案,例如std :: memcpy,std :: bit_cast(C ++ 20),未签名的char*,工会。
4。我可以使用reinterpret_cast逃脱严格的别名吗?
不,reinterpret_cast并不能阻止严格的别名违规行为,并导致不确定的行为。
5。为什么char*和未签名的char*对严格的混叠有特殊含义?
在法律上允许它们别名任何类型,这些类型可用于安全访问原始内存。