为什么优化会破坏此功能?

时间:2014-01-04 15:08:03

标签: c++ c optimization endianness strict-aliasing

我们最近在大学里开了一个关于多种语言编程特色的讲座。

讲师写下了以下功能:

inline u64 Swap_64(u64 x)
{
    u64 tmp;
    (*(u32*)&tmp)       = Swap_32(*(((u32*)&x)+1));
    (*(((u32*)&tmp)+1)) = Swap_32(*(u32*) &x);

    return tmp;
}

虽然我完全理解这在可读性方面也是非常差的风格,但他的主要观点是这部分代码在生产代码中运行良好,直到它们实现了高优化级别。然后,代码就什么都不做。

他说变量tmp的所有赋值都将由编译器优化。但为什么会发生这种情况呢?

我理解在某些情况下需要将变量声明为 volatile ,这样编译器就不会触及它们,即使他认为它们永远不会被读取或写入但我不知道为什么这会发生在这里。

3 个答案:

答案 0 :(得分:47)

在C ++中,如果指针参数指向根本不同的类型("严格别名"规则),则假定指针参数不是别名(char*除外)。这允许一些优化。

此处,u64 tmp永远不会修改为u64 u32*的内容已被修改,但可能与u64 tmp'无关。因此nop可能会被视为u64 tmp

答案 1 :(得分:41)

此代码违反了strict aliasing rules,这使得通过不同类型的指针访问对象是非法的,尽管允许通过* char **进行访问。允许编译器假设不同类型的指针不指向相同的存储器并相应地进行优化。这也意味着代码调用undefined behavior并且可以真正做任何事情。

本主题的最佳参考之一是Understanding Strict Aliasing,我们可以看到第一个示例与OP的代码类似:

uint32_t swap_words( uint32_t arg )
{
  uint16_t* const sp = (uint16_t*)&arg;
  uint16_t        hi = sp[0];
  uint16_t        lo = sp[1];

  sp[1] = hi;
  sp[0] = lo;

 return (arg);
} 

本文解释此代码违反了严格别名规则,因为sparg的别名,但它们有不同的类型,并说尽管它会编译,但很可能arg返回后,swap_words将保持不变。虽然通过简单的测试,我无法使用上面的代码和OP代码重现该结果,但这并不意味着什么,因为这是未定义的行为,因此无法预测。

本文接着讨论了许多不同的案例,并通过一个联合提出了一些工作解决方案,包括 type-punning ,这在 C99 中有明确定义1 并且可能在 C ++ 中未定义,但实际上大多数主要编译器都支持,例如gcc's reference on type-punning。前一个帖子Purpose of Unions in C and C++进入了血腥细节。虽然这个主题有许多主题,但这似乎做得最好。

该解决方案的代码如下:

typedef union
{
  uint32_t u32;
  uint16_t u16[2];
} U32;

uint32_t swap_words( uint32_t arg )
{
  U32      in;
  uint16_t lo;
  uint16_t hi;

  in.u32    = arg;
  hi        = in.u16[0];
  lo        = in.u16[1];
  in.u16[0] = lo;
  in.u16[1] = hi;

  return (in.u32);
}

供参考,C99 draft standard 严格别名的相关部分为6.5 表达式 7

  

对象的存储值只能由具有以下类型之一的左值表达式访问: 76)

     
    

- 与对象的有效类型兼容的类型,

         

- 与对象的有效类型兼容的类型的限定版本

         

- 对应于有效类型的有符号或无符号类型     对象,

         

- 对应于合格版本的有符号或无符号类型的类型     有效的对象类型,

         

- 包含其中一种上述类型的聚合或联合类型     成员(包括,递归地,子集合或包含的联合的成员),或

         

- 字符类型。

  

脚注76 说:

  

此列表的目的是指定对象可能或可能没有别名的情况。

C++ draft standard的相关部分为3.10 Lvalues和rvalues 10

文章Type-punning and strict-aliasing提供了一个更温和但不太完整的主题介绍,C99 revisited C99 和别名进行了深入分析,并且不是轻松阅读。对Accessing inactive union member - undefined?的回答通过 C ++ 中的联合来解决类型惩罚的泥泞细节,也不是轻松阅读。


脚注:

  1. Pascal Cuoq引用comment [...] C99最初是笨拙的措辞,似乎通过工会进行类型惩罚未定义。实际上,虽然工会的类型惩罚在C89中是合法的,在C11中是合法的,并且它在C99中一直是合法的,尽管委员会在2004年之前修正了不正确的措辞以及随后的TC3发布。 open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm

答案 2 :(得分:10)

g ++(Ubuntu / Linaro 4.8.1-10ubuntu9)4.8.1:

> g++ -Wall -std=c++11 -O0 -o sample sample.cpp

> g++ -Wall -std=c++11 -O3 -o sample sample.cpp
sample.cpp: In function ‘uint64_t Swap_64(uint64_t)’:
sample.cpp:10:19: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     (*(uint32_t*)&tmp)       = Swap_32(*(((uint32_t*)&x)+1));
                   ^
sample.cpp:11:54: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     (*(((uint32_t*)&tmp)+1)) = Swap_32(*(uint32_t*) &x);
                                                      ^

Clang 3.4不会在任何优化级别which is curious ...

中发出警告