4

我尝试了以下代码作为实现单词RB字节交换的天真尝试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));
}

令我惊讶的是,这段代码在 C++17 模式下的 GCC 中“有效”——字节被交换了

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

但它不应该交换字节!C++17 明确指出,赋值的 RHS 应该在 LHS 之前 [完全] 排序,这也适用于复合赋值。这意味着在上面的表达式中,每个 RHS 都^=应该使用 的原始值abgr。因此,最终结果abgr应该只是B逐字节进行异或运算R。这就是 Clang 似乎产生的(有趣的是,带有排序警告)

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

快速浏览 GCC 程序集

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

显示它似乎是倒序排列的:LHS 在 RHS 之前。这是一个错误吗?或者这是海湾合作委员会的某种有意识的决定?还是我误解了什么?

4

2 回答 2

3

完全相同的行为表现在int a = 1; (a += a) += a;, GCCa == 4之后计算和 clang a == 3

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

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

第 1 段:赋值运算符 (=) 和复合赋值运算符都从右到左分组。都需要一个可修改的左值作为左操作数;它们的结果是一个引用左操作数的左值。如果左操作数是位域,则所有情况下的结果都是位域。在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。右操作数在左操作数之前排序。对于不确定顺序的函数调用,复合赋值的操作是单次求值。

第 7 段:形式为 E1 op = E2 的表达式的行为等价于 E1 = E1 op E2,只是 E1 仅计算一次。在 += 和 -= 中,E1 要么具有算术类型,要么是指向可能有 cv 限定的完全定义对象类型的指针。在所有其他情况下,E1 应具有算术类型。

GCC 似乎正在使用此规则在内部(a += a) += a转换为(a = a + a) += ato 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:第 7 段 > 第 1 段;clang:第 1 段 > 第 7 段)。

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

于 2018-07-25T08:55:31.360 回答
0

不要让事情变得比必要的更复杂。您可以以一种相当直接的方式交换这 2 个组件,而无需将自己描绘成语言的黑暗角落:

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 上,它使用rol3 个按位运算符的单指令来生成grab

ABGR_to_ARGB(unsigned int):
        mov     eax, edi
        and     edi, -16711936
        rol     eax, 16
        and     eax, 16711935
        or      eax, edi
        ret
于 2018-07-25T09:59:51.043 回答