代码之家  ›  专栏  ›  技术社区  ›  AnT stands with Russia

赋值中的C++ 17排序:在GCC中仍然没有实现?

  •  4
  • AnT stands with Russia  · 技术社区  · 7 年前

    我尝试了下面的代码,作为实现 R B 字节 ABGR 单词

    #include <stdio.h>
    #include <stdint.h>
    
    uint32_t ABGR_to_ARGB(uint32_t abgr)
    {
      return ((abgr ^= (abgr >> 16) & 0xFF) ^= (abgr & 0xFF) << 16) ^= (abgr >> 16) & 0xFF;
    }
    
    int main() 
    {
        uint32_t tmp = 0x11223344;
        printf("%x %x\n", tmp, ABGR_to_ARGB(tmp));
    }
    

    令我惊讶的是,这个代码在GCC中工作在C++ 17模式中——字节被交换了。

    http://coliru.stacked-crooked.com/a/43d0fc47f5539746

    但它不应该交换字节!C++ 17清楚地说明赋值的RHS在LHS之前应该被完全排序,这也适用于复合赋值。这意味着在上面的表达式中 ^= 应该使用 abgr 是的。因此最终的结果是 ABGR公司 应该有 字节异或 字节。这就是clang似乎产生的结果(有趣的是,带有排序警告)

    http://coliru.stacked-crooked.com/a/eb9bdc8ced1b5f13

    快速查看gcc程序集

    https://godbolt.org/g/1hsW5a

    揭示了它似乎是倒序的:左手在右手之前。这是虫子吗?或者这是海合会方面有意识的决定?还是我误会了什么?

    2 回复  |  直到 7 年前
        1
  •  3
  •   hoffmale    7 年前

    同样的行为表现在 int a = 1; (a += a) += a; ,GCC计算 a == 4 后来又叮当作响 a == 3 是的。

    潜在的歧义源于标准的这一部分(工作草案N4762):

    [expr.ass]:7.6.18赋值和复合赋值运算符

    第1段:赋值运算符(=)和复合赋值运算符都是从右向左分组的。都需要一个 可修改的左值作为其左操作数;其结果是指左操作数的左值。结果是 如果左操作数是位字段,则cases是位字段。在所有情况下,赋值都是在值之后排序的 左右操作数的计算,以及赋值表达式的值计算之前。 右操作数在左操作数之前排序。关于一个不确定的序列 函数调用时,复合赋值的操作是单次求值。

    第7段:形式为e1 op=e2的表达式的行为等同于e1=e1 op e2,但e1是 只评估一次。在+=和-=中,e1应具有算术类型或是指向 cv限定的完全定义的对象类型。在所有其他情况下,e1应具有算术类型。

    gcc似乎使用这个规则来内部转换 (a += a) += a (a = a + a) += a a = (a = a + a) + a (自 a = a + a 必须只计算一次)-对于此表达式,将正确应用排序规则。

    然而,clang似乎以不同的方式完成了最后一个转换步骤: auto temp = a + a; temp = temp + a; a = temp;

    不过,两个编译器都对此发出警告(从原始代码看):

    • 合同一般条款: warning: operation on 'abgr' may be undefined [-Wsequence-point]

    • 叮当声: warning: unsequenced modification and access to 'abgr' [-Wunsequenced]

    因此,编译器编写人员知道这种模糊性,并决定以不同的方式排列优先级(gcc:parage7>parage1;clang:parage1>parage7)。

    这似乎是标准中的一个缺陷。

        2
  •  0
  •   Maxim Egorushkin    7 年前

    不要把事情弄得太复杂。您可以以相当简单的方式交换这两个组件,而无需将自己绘制到语言的黑暗角落:

    uint32_t ABGR_to_ARGB(uint32_t abgr) {
        constexpr uint32_t mask = 0xff00ff00;
        uint32_t grab = abgr >> 16 | abgr << 16;
        return (abgr & mask) | (grab & ~mask);
    }
    

    它还生成比原始版本好得多的程序集。在x86上,它使用单 rol 三位运算符生成的指令 grab 以下内容:

    ABGR_to_ARGB(unsigned int):
            mov     eax, edi
            and     edi, -16711936
            rol     eax, 16
            and     eax, 16711935
            or      eax, edi
            ret
    
    推荐文章