当前位置: 首页 > 知识库问答 >
问题:

为什么优化取消了这个功能?

殷安顺
2023-03-14

我们最近在大学里做了一个关于几种语言的编程专题的讲座。

讲师写下了以下功能:

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

    return tmp;
}

虽然我完全理解就可读性而言这也是非常糟糕的风格,但他的主要观点是,这部分代码在生产代码中工作得很好,直到它们实现了高优化级别。然后,代码将什么也不做。

他说所有对变量< code>tmp的赋值都会被编译器优化掉。但是为什么会这样呢?

我知道在某些情况下,变量需要声明为易失性,以便编译器不会触及它们,即使他认为它们从未被读取或写入,但我不知道为什么会在这里发生这种情况。

共有3个答案

柳浩大
2023-03-14

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没有在任何优化级别发出警告,这很奇怪...

白泽语
2023-03-14

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

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

唐威
2023-03-14

这段代码违反了严格的别名规则,这使得通过不同类型的指针访问对象是非法的,尽管通过*char **访问是允许的。允许编译器假设不同类型的指针不指向同一个内存,并相应地进行优化。这也意味着代码调用了未定义的行为,可以做任何事情。

本主题的最佳参考之一是理解严格别名,我们可以看到第一个示例与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);
} 

这篇文章解释了该代码违反了严格的别名规则,因为< code>sp是< code>arg的别名,但是它们具有不同的类型,并且说尽管它将编译,但是在< code>swap_words返回之后,很可能< code>arg将保持不变。虽然对于简单的测试,我无法用上面的代码或OPs代码重现结果,但这并不意味着什么,因为这是未定义的行为,因此是不可预测的。

本文接着讨论了许多不同的情况,并提出了几个工作解决方案,包括通过联合进行类型双关,这在C991中定义良好,在C中可能未定义,但在实践中得到大多数主要编译器的支持,例如,这里是gcc关于类型双关的参考。之前的C和C中Unions的线程目的深入了血淋淋的细节。虽然有很多关于这个主题的线程,但这似乎做得最好。

该解决方案的代码如下:

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标准草案中关于严格别名的相关章节是6.5Expressions第7段,其中规定:

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

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

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

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

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

— 在其成员中包括上述类型之一的聚合或联合类型(以递归方式包括子聚合或包含的联合的成员),或

— 字符类型。

脚注76说:

此列表的目的是指定对象可以使用别名,也可以不使用别名的情况。

C标准草案的相关部分是3.10左值和右值第10段

类型双关语和严格混淆现象这篇文章对这个话题进行了更温和但不太完整的介绍,C99的重新审视对C99和混淆现象进行了深入的分析,这不是轻松阅读。访问不活跃的联合成员-未定义?的这个答案通过C中的联合讨论了类型双关语的模糊细节,也不是轻松阅读。

脚注:

  1. 引用Pascal Cuoq的评论:[…]C99最初措辞笨拙,似乎使通过工会的打字双关语未定义。事实上,尽管工会的打字双关语在C89是合法的,在C11是合法的,在C99也是合法的,尽管直到2004年委员会才纠正错误的措辞,以及随后TC3的发布。open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
 类似资料:
  • 我写了一个函数: 我想的是用一个“cur”来记录这两个链表“a”和“b”中的每个节点。然后这两个链表'a'和'b'移动到它的下一个节点。然后转到下一个WHILE循环。 然而,这是错误的。当我调试时,在这个第一个WHILE循环中,当它完成这个

  • 它将把牌阵的一半分配给玩家和计算机(玩家得到前半部分,计算机得到后半部分)。现在这是先洗牌,所以是的,这似乎是公平的。 我得到的出界错误是这一行:

  • 为什么gcc在上的末尾插入了,而我却要自己在上添加? 从:

  • 嘿,我不能正确理解这个JavaScript代码: 输出 所以我知道前四个输出的代码内部发生了什么,但无法理解最后四个输出的代码内部发生了什么,请有人详细解释最后四个输出的代码,这将对我真正有帮助。

  • 有问题的部分在类App extends react.component下。我正在尝试用update方法中的状态更改重新呈现Leader组件。它会更改状态,但不会更改传递给Leader到Rerender的属性。

  • 本文向大家介绍假如你是微信的产品经理,你觉得微信最应该优化的功能是什么,原因是什么?为什么微信没有做出这个优化?相关面试题,主要包含被问及假如你是微信的产品经理,你觉得微信最应该优化的功能是什么,原因是什么?为什么微信没有做出这个优化?时的应答技巧和注意事项,需要的朋友参考一下 附近的人添加视频动态。 在附近的人的基础上增进一个方便他人了解和切入话题的维度,同时起到普及和传播动态视频的作用。打开方