现代c++编译器在类型转换后优化赋值吗

Do modern c++ compilers optimize assignments after type casting?

本文关键字:优化 赋值 类型转换 c++ 编译器 现代      更新时间:2024-09-30

获取以下代码:

char chars[4] = {0x5B, 0x5B, 0x5B, 0x5B};
int* b = (int*) &chars[0];

(int*) &chars[0]值将用于循环(长循环)。在我的代码中使用(int*) &chars[0]比使用b有什么优势吗?创建b是否有任何开销?因为我只想把它用作别名,并提高代码的可读性。

另外,只要我知道自己在做什么,就可以做这种类型的铸造吗?或者我应该始终将memcpy()连接到另一个具有正确类型的数组并使用它吗?我会遇到任何未定义的行为吗?因为到目前为止,在我的测试中,它是有效的,但我看到人们不鼓励这种类型的选角。

只要我知道自己在做什么,就可以进行这种类型转换吗?

不,这不好。这不安全。C++标准不允许这样做。您可以访问对象表示(即将对象指针投射到char*),尽管结果取决于目标平台(由于字节序和填充)。然而,你不能安全地做相反的事情(即没有未定义的行为)。

更具体地说,int类型可以具有与char(未对齐)不同的对齐要求(通常对齐到4或8个字节)。因此,当b被取消引用时,您的数组可能没有对齐,并且强制转换会导致未定义的行为。请注意,尽管主流x86-64处理器支持这种情况,但它可能会导致某些处理器(例如AFAIK、POWER)崩溃。此外,编译器可以假设b在内存中对齐(而不是alignof(int))。

或者我应该总是将memcpy()添加到另一个具有正确类型的数组并使用它吗?

是,或者可供选择的C++操作,如自C++20以来可用的新std::bit_cast。不要担心性能:大多数编译器(GCC、Clang、ICC,当然还有MSVC)都会优化此类操作(称为类型punning)。

我会遇到任何未定义的行为吗?

如前所述,是的,只要类型双关没有正确执行。有关这方面的更多信息,您可以阅读以下链接:

  • 什么是严格别名规则?我们为什么关心它
  • interpret_cast转换
  • 对象和路线

因为到目前为止,在我的测试中,它是有效的,但我看到人们不鼓励这种类型的铸造。

它通常适用于x86-64处理器上的简单示例。然而,当您处理一个大代码时,编译器确实会执行一些愚蠢的优化(但对于C++标准来说,这些优化是完全正确的)。引用cppreference:"编译器不需要诊断未定义的行为(尽管诊断了许多简单的情况),并且编译后的程序也不需要做任何有意义的事情&";。这样的问题很难调试,因为它们通常只在启用优化时以及在某些特定情况下才会出现。关于函数的内联,程序的结果可能会发生变化,这取决于编译器的启发法。在您的情况下,这取决于堆栈的对齐,而堆栈的对齐取决于编译器优化和当前作用域中声明/使用的变量。某些处理器不支持未对齐的访问(例如,跨越缓存线边界的访问),这会导致数据损坏的硬件异常。

所以简单地说;它工作";到目前为止,并不意味着它在任何时候都能在任何地方发挥作用。

(int*)&chars[0]值将在循环(长循环)中使用。使用(int*)&我的代码中的字符[0]超过了b?创建b有任何开销吗?因为我只想把它用作别名,并提高代码的可读性。

假设使用正确的方法进行类型双关(例如memcpy),那么只要启用了优化标志,优化编译器就可以完全优化此初始化。您不应该担心这一点,除非您发现生成的代码优化得很差正确性比性能更重要

AFAIK,C编译器在投射指针时不插入任何代码,这意味着charsb都只是内存地址。通常,C++编译器应该以与C编译器相同的方式编译它——这就是C++具有不同的、更高级的强制转换语义的原因。

但您可以编译它,然后在gdb中对其进行反汇编,以便自己查看。

否则,只要您知道异常平台上的字节序问题或潜在的不同int大小,您的选角是安全的。

另请参阅这个问题:在C中,投射指针是否有开销?

如果代码使用从指向需要字对齐的类型的指针派生的指针执行多个离散字节操作,clang有时会用一个字写入替换离散写入,如果原始对象针对该字类型对齐,则该字写入会成功,但是如果对象没有按照编译器期望的方式对齐,那么在不支持未对齐访问的系统上会失败。

除其他外,这意味着,如果将指向T的指针强制转换为指向包含T的并集的指针,则如果并集包含任何需要比原始类型更严格对齐的类型,则尝试使用并集指针访问原始类型的代码可能会失败,即使并集仅通过原始类型的成员访问。